Bit Manipulation · 10 of 10
Power Set
List every possible bag you could pack from a set of items, by letting the bits of the numbers 0 to 2^n - 1 say which items go in.
- Simple way
- time O(n * 2^n)
room O(n) - Clever way
- time O(n * 2^n)
room O(1)
The problem, simply
You get a list of n different items. Give back every subset: every group you can make by picking some of the items, in any amount from none to all. The empty group counts, and so does the whole list.
With n items there are 2^n subsets, because each item is either in or out. Space for the answer does not count as extra room below.
A story to remember it
Picture it
Items 1, 2 and 3. Each number from 0 to 7 is a mask. Bit 0 says if 1 is in, bit 1 says if 2 is in, bit 2 says if 3 is in. The row label shows the subset each mask picks.
Examples
Three items
- In
[1, 2, 3]- Out
[[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
2^3 = 8 subsets. This order comes from counting masks 0 to 7. Most problems accept any order.
Two items
- In
[4, 9]- Out
[[], [4], [9], [4,9]]
2^2 = 4 subsets.
One item
- In
[7]- Out
[[], [7]]
Leave it out or put it in.
No items
- In
[]- Out
[[]]
There is exactly one subset of nothing: the empty one. The answer is a list holding one empty list, not an empty list.
Way 1: the simple way
Walk through the items one by one and make a choice for each: leave it out, or put it in. Each choice splits the work into two branches, so the choices form a tree. Every leaf at the bottom is one finished subset.
- Start with an empty bag at item 0.
- Branch one: skip the item and go to the next.
- Branch two: put the item in, go to the next, then take it back out.
- When all items are decided, copy the bag into the answer.
The choice tree for 1, 2, 3. Each edge marked n skips an item and each edge marked y takes it. The 8 leaves at the bottom are the 8 subsets.
- answer
import java.util.*;
class Solution {
public static List<List<Integer>> powerSet(int[] nums) {
List<List<Integer>> out = new ArrayList<>();
choose(nums, 0, new ArrayList<>(), out);
return out;
}
private static void choose(int[] nums, int i, List<Integer> bag, List<List<Integer>> out) {
if (i == nums.length) {
out.add(new ArrayList<>(bag)); // every item decided: save a copy
return;
}
choose(nums, i + 1, bag, out); // leave nums[i] out
bag.add(nums[i]); // put nums[i] in
choose(nums, i + 1, bag, out);
bag.remove(bag.size() - 1); // take it back out
}
}How long it takes: 2^n leaves, and copying each bag costs up to n, O(n * 2^n). Extra room: the call stack and the bag are n deep, O(n).
Way 2: the clever way
A number from 0 to 2^n - 1 has n bits. Treat each bit as the switch for one item. Every number gives a different pattern of switches, and every pattern shows up once. So counting from 0 to 2^n - 1 lists every subset, with no recursion.
- Let
total = 1 << n, which is2^n. - For each
maskfrom 0 tototal - 1, make a new bag. - For each
jfrom 0 ton - 1, if bitjofmaskis on, putnums[j]in the bag. - Add the bag to the answer.
Count from 0 to 7 and read the switches
01 / 10
Each of the 3 items is either in or out, so there are 2 times 2 times 2 = 8 bags. We count mask from 0 to 7. Bit j of the mask says whether item j is in the bag. Bit 0 is item 1, bit 1 is item 2, bit 2 is item 3.
- masks
- 0 to 7
- looking now
- answer
- rejected
- just added
import java.util.*;
class Solution {
public static List<List<Integer>> powerSet(int[] nums) {
int n = nums.length;
List<List<Integer>> out = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) {
List<Integer> bag = new ArrayList<>();
for (int j = 0; j < n; j++) {
if ((mask & (1 << j)) != 0) {
bag.add(nums[j]); // switch j is up: item j goes in
}
}
out.add(bag);
}
return out;
}
}How long it takes: 2^n masks, each checking n bits, O(n * 2^n). Extra room besides the answer: the counters, O(1). No call stack at all.
Watch out for
- In Way 1, adding
bagitself instead of a copy. All saved bags would be the same list, and they end up empty after backtracking. - Checking bit
jwith(mask & (1 << j)) == 1. That is only right forj = 0. Use!= 0, or((mask >> j) & 1) == 1. - Forgetting the empty subset, or returning an empty list for empty input instead of a list with one empty list.
What interviewers ask next
- The items have repeats, like
[1, 2, 2]? Sort first. In the choice tree, after skipping a value, skip all its copies too, so[1, 2]is not made twice. - Only subsets of size
k? Use the choice tree and stop a branch when the bag is full, or loop masks and keep those withInteger.bitCount(mask) == k. - Visit subsets so that each one differs from the last by one item? Use Gray code: mask
i ^ (i >> 1). - Another simple way? Start with
[[]]. For each item, copy every bag you have and add the item to the copies. That doubles the list each time. - Loop over all subsets of a given mask
m?for (int s = m; s > 0; s = (s - 1) & m), a classic in bitmask DP.
Remember this
Grown-up words: the bag is a subset, the list of all bags is the power set, the light switches are a bitmask, and the skip or take tree is backtracking.