Topics › Bit Manipulation › Interview Problems
Power Set Bit Manipulation
LeetCode ↗ Java source ↗ Edit this article ↗
Jump to code:
Problem
You are given an array nums of n distinct integers. Return its power set: every possible subset, including the empty set and the full set, in any order. There are exactly 2^n of them. The TUF statement gives a string instead of an array and asks for all of its subsequences; it is the same problem, since a subsequence is just a subset of positions, and the Java file includes that version too.
Constraints: 0 ≤ n ≤ 16 (the output alone has 2^n entries, so n is always small), values fit in a 32-bit signed integer and are pairwise distinct.
Example 1
nums = [1, 2, 3]
answer = [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]] // 2^3 = 8 subsets
Example 2
nums = [5]
answer = [[], [5]]
Example 3
nums = []
answer = [[]] // the empty set has exactly one subset: itself
Approach 1: Recursion, take or skip each element
Intuition. Build a subset one decision at a time. Standing at index index, there are only two things to do with nums[index]: leave it out or put it in. Make both choices recursively; when index reaches n, every element has been decided and the current list is one complete subset. Because every element has exactly two options and the choices are independent, the recursion tree has exactly 2^n leaves, one per subset, and no subset is produced twice.
Algorithm.
- Start with
index = 0and an emptycurrentlist. - If
index == n, copycurrentinto the result and return. - Recurse with
index + 1without addingnums[index](skip). - Append
nums[index], recurse withindex + 1(take), then remove it again so the caller seescurrentunchanged (backtracking).
static List<List<Integer>> recursion(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
buildSubsets(nums, 0, new ArrayList<>(), result);
return result;
}
private static void buildSubsets(int[] nums, int index, List<Integer> current, List<List<Integer>> result) {
if (index == nums.length) {
result.add(new ArrayList<>(current));
return;
}
buildSubsets(nums, index + 1, current, result); // skip nums[index]
current.add(nums[index]); // take nums[index]
buildSubsets(nums, index + 1, current, result);
current.remove(current.size() - 1); // undo the choice before returning
}
Complexity. Time O(2^n × n): there are 2^n leaves and copying a subset costs up to n. Space O(n) for the recursion stack and the shared current list, not counting the output.
Two details matter. The copy new ArrayList<>(current) is essential; adding current itself would put the same mutable list into the result 2^n times. And current.remove(current.size() - 1) calls the remove(int index) overload, which is what we want; remove(Integer.valueOf(x)) would search by value and be both slower and wrong if values could repeat.
This approach is perfectly fine and generalises to combinations, permutations and pruning. Its only drawbacks are recursion overhead and the fact that the order of the output depends on the traversal.
Approach 2: Optimal, enumerate bitmasks
Intuition. A subset of n elements is a yes/no decision for each position: n bits. An n-bit number is such a sequence of decisions. So the integers 0, 1, 2, ..., 2^n - 1 are in one-to-one correspondence with the subsets: read bit i of the mask as “is nums[i] included?”. Enumerating all subsets becomes a plain counting loop, and extracting a subset becomes testing n bits. There is no recursion, no backtracking state and the output order is fixed and predictable.
Algorithm.
- Let
total = 1 << n(that is2^n). - For every
maskfrom0tototal - 1: - For every position
ifrom0ton - 1, if bitiofmaskis set ((mask & (1 << i)) != 0), appendnums[i]to the current subset. - Add the subset to the result.
static List<List<Integer>> optimal(int[] nums) {
int n = nums.length;
int total = 1 << n; // 2^n subsets
List<List<Integer>> result = new ArrayList<>(total);
for (int mask = 0; mask < total; mask++) {
List<Integer> subset = new ArrayList<>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) subset.add(nums[i]);
}
result.add(subset);
}
return result;
}
Dry run on nums = [1, 2, 3]:
| mask | binary (bit2 bit1 bit0) | included positions | subset |
|---|---|---|---|
| 0 | 000 | none | [] |
| 1 | 001 | 0 | [1] |
| 2 | 010 | 1 | [2] |
| 3 | 011 | 0, 1 | [1, 2] |
| 4 | 100 | 2 | [3] |
| 5 | 101 | 0, 2 | [1, 3] |
| 6 | 110 | 1, 2 | [2, 3] |
| 7 | 111 | 0, 1, 2 | [1, 2, 3] |
Every subset appears exactly once because every integer in [0, 2^n) has exactly one binary representation.
Complexity. Time O(2^n × n): 2^n masks, each tested against n bits. Space O(1) beyond the output. The asymptotics match the recursion, but the constant is smaller and the loop is trivially iterative.
Why (mask & (1 << i)) != 0 rather than == 1? mask & (1 << i) is either 0 or 1 << i, not 0 or 1. Comparing against 1 would only ever be true for i = 0. The alternative ((mask >> i) & 1) == 1 shifts the bit down first, and that can be compared with 1. Both forms are used in the Java file.
The string version
The TUF statement hands you a string s and asks for all subsequences. Nothing changes except that a StringBuilder collects the chosen characters:
static List<String> subsequences(String s) {
int n = s.length();
List<String> result = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) {
StringBuilder sb = new StringBuilder();
for (int i = 0; i < n; i++) {
if (((mask >> i) & 1) == 1) sb.append(s.charAt(i));
}
result.add(sb.toString());
}
return result;
}
For s = "abc" this yields "", "a", "b", "ab", "c", "ac", "bc", "abc" in mask order. If the string has repeated characters, such as "aa", the output contains "a" twice because the two occurrences come from different positions; de-duplicate with a Set if the problem wants distinct strings.
Edge cases and pitfalls
- Empty input.
n = 0givestotal = 1and the loop produces exactly one subset, the empty one. That is correct: the empty set has one subset. - Shift overflow.
1 << nis only valid forn ≤ 30in anint;1 << 31is negative and1 << 32wraps around to1. The problem boundsnwell below that, and in practice2^nlists would not fit in memory anyway. If you ever neednup to 62, use1L << nand alongmask. - Mask order versus recursion order. The bitmask loop emits subsets by increasing mask value; the recursion above emits the skip branch first and so starts with
[]but then produces[3], [2], [2, 3], [1], .... Both are valid answers; the tests normalise before comparing. - Shared mutable list in recursion. Forgetting to copy
currentat the leaf is the single most common bug: the result ends up holding2^nreferences to one list that is empty by the time the function returns. - Duplicates in
nums. This problem promises distinct values. If duplicates are allowed, the plain power set contains repeated subsets; sort first and skip a value that equals its predecessor when it was not taken (Subsets II). - Output size. The result is exponential, so “optimise” only ever refers to the constant factor. There is no polynomial algorithm for an exponential-size output.
Related problems
- Power Set is the recursion-focused version of this exact problem.
- Subsets I asks for the sums of all subsets and uses the same take/skip recursion.
- Introduction to Bits and Tricks explains the shift and mask operations used here.
- Check if the i-th bit is Set or Not is the single bit test that drives the inner loop.