Bit masks for subsets
A bitmask packs a subset of n items into a single integer: bit i is 1 when item i is chosen. Set operations become single instructions, and all 2^n subsets can be enumerated with a simple loop, which is the basis of bitmask dynamic programming.
Learning goals
- Represent a subset as an int or long and perform set operations with bit operators
- Enumerate all subsets and all submasks of a mask
- Iterate over only the set bits of a mask
- Describe a state-transition over masks and the idea of bitmask DP
Prerequisites
If a pizza has 5 optional toppings, one 5-bit number describes the whole order: 10110 means toppings 1, 2 and 4. Checking whether two orders share a topping is a single AND.
Represent subset
For a universe of n ≤ 31 items (63 with long), the subset with items i1, i2, … is the number (1 << i1) | (1 << i2) | …. Every subset gets a unique number between 0 and 2^n - 1, and the empty set is 0.
Set operations map directly onto bit operators.
| Set operation | Bitmask expression |
|---|---|
| Contains i | (mask >> i & 1) == 1 |
| Add i | mask | (1 << i) |
| Remove i | mask & ~(1 << i) |
| Union | a | b |
| Intersection | a & b |
| Difference a \ b | a & ~b |
| Complement (n items) | ((1 << n) - 1) ^ mask |
| Is a ⊆ b | (a & b) == a |
| Size | Integer.bitCount(mask) |
Enumerate subsets
The numbers 0 to 2^n - 1 are exactly all subsets of n items. Loop mask over that range and, for each, add item i when bit i is set. The animation lists every subset in order and highlights the chosen items.
The total cost is O(2^n · n). That is fine up to about n = 20-25 (a million to 33 million subsets), and impossible beyond that: the count doubles with every additional item.
1static List<List<Integer>> allSubsets(int[] items) {2int n = items.length;3List<List<Integer>> result = new ArrayList<>();4for (int mask = 0; mask < (1 << n); mask++) {5List<Integer> subset = new ArrayList<>();6for (int i = 0; i < n; i++) {7if ((mask & (1 << i)) != 0) subset.add(items[i]);8}9result.add(subset);10}11return result;12}
Worst
O(n · 2^n)
Space
O(n · 2^n) to store them all
Enumerate all subsets with a bitmask
Interactive · edit the inputint[] arr · length 3
subsets so far
3 items give 2^3 = 8 subsets. Every number from 0 to 7 is one choice: bit i says whether item i is in.
1int n = items.length;2for (int mask = 0; mask < (1 << n); mask++) {3List<Integer> subset = new ArrayList<>();4for (int i = 0; i < n; i++) {5if ((mask & (1 << i)) != 0) { // is bit i set?6subset.add(items[i]);7}8}9print(subset);10}
Live state
n
3
2^n
8
Execution trace (dry run table)Show 10 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L2 | n=3 2^n=8 | 3 items give 2^3 = 8 subsets. Every number from 0 to 7 is one choice: bit i says whether item i is in. |
| 2 | L5 | mask=0 subset={} | mask 0 has no bits set: the empty subset. |
| 3 | L5 | mask=1 subset={5} | mask 1 = 001 in binary. Bits 0 are set, so the subset is {5}. |
| 4 | L5 | mask=2 subset={7} | mask 2 = 010 in binary. Bits 1 are set, so the subset is {7}. |
| 5 | L5 | mask=3 subset={5, 7} | mask 3 = 011 in binary. Bits 0, 1 are set, so the subset is {5, 7}. |
| 6 | L5 | mask=4 subset={9} | mask 4 = 100 in binary. Bits 2 are set, so the subset is {9}. |
| 7 | L5 | mask=5 subset={5, 9} | mask 5 = 101 in binary. Bits 0, 2 are set, so the subset is {5, 9}. |
| 8 | L5 | mask=6 subset={7, 9} | mask 6 = 110 in binary. Bits 1, 2 are set, so the subset is {7, 9}. |
| 9 | L5 | mask=7 subset={5, 7, 9} | mask 7 = 111 in binary. Bits 0, 1, 2 are set, so the subset is {5, 7, 9}. |
| 10 | L10 | subsets=8 | 8 subsets generated in O(2^n · n). This only works for small n (up to about 20-25), because the count doubles with every item. |
Enumerate submasks
To enumerate every subset of a given mask m, start at sub = m and repeatedly step to the next smaller submask with sub = (sub - 1) & m. Subtracting 1 may switch on bits outside m; ANDing with m removes them and lands on the next submask in decreasing order. Stop when sub reaches 0 and handle the empty submask separately.
A mask with k set bits has 2^k submasks, and enumerating all submasks of all masks of n bits costs 3^n in total (each item is in the mask and in the submask, in the mask but not the submask, or in neither).
1for (int sub = mask; sub > 0; sub = (sub - 1) & mask) {2process(sub);3}4process(0); // the empty submask
Enumerate the submasks of a mask
Interactive · edit the inputmask = 22
submasks so far
22 has 3 set bits, so it has 2^3 = 8 submasks. Start with sub = mask.
1for (int sub = mask; sub > 0; sub = (sub - 1) & mask) {2process(sub); // next submask, in decreasing order3}4process(0); // the empty submask
Live state
mask
22
Execution trace (dry run table)Show 9 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | mask=22 | 22 has 3 set bits, so it has 2^3 = 8 submasks. Start with sub = mask. |
| 2 | L2 | sub=22 sub - 1=21 next=20 | Process 010110. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 010100. |
| 3 | L2 | sub=20 sub - 1=19 next=18 | Process 010100. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 010010. |
| 4 | L2 | sub=18 sub - 1=17 next=16 | Process 010010. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 010000. |
| 5 | L2 | sub=16 sub - 1=15 next=6 | Process 010000. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 000110. |
| 6 | L2 | sub=6 sub - 1=5 next=4 | Process 000110. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 000100. |
| 7 | L2 | sub=4 sub - 1=3 next=2 | Process 000100. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 000010. |
| 8 | L2 | sub=2 sub - 1=1 next=0 | Process 000010. Next: sub - 1 steps down by one and may land on bits outside the mask; ANDing with the mask removes them and jumps to the next smaller submask: 000000. |
| 9 | L4 | submasks=8 | The loop stops at sub = 0, so the empty submask is handled separately. 8 submasks in total. |
Iterate set bits
To visit only the 1 bits of a mask, repeatedly take the lowest set bit and remove it: for (m = mask; m != 0; m &= m - 1), with Integer.numberOfTrailingZeros(m) giving its index. This runs once per set bit, so sparse masks are cheap.
This is the standard way to loop over the members of a subset, or over the neighbours of a vertex stored as a bitmask in a graph algorithm.
Worst
O(number of set bits)
Space
O(1)
Iterate over the set bits of a mask
Interactive · edit the inputmask = 148
indices visited
Visit only the positions of the 1 bits, skipping every zero.
1for (int m = mask; m != 0; m &= m - 1) {2int lowest = m & -m; // isolate the lowest set bit3int index = Integer.numberOfTrailingZeros(m); // its position4visit(index);5}
Live state
mask
148
Execution trace (dry run table)Show 5 stepsHide
| Step | Line | State | What happens |
|---|---|---|---|
| 1 | L1 | mask=148 | Visit only the positions of the 1 bits, skipping every zero. |
| 2 | L3 | m=148 lowest=4 index=2 | m & -m isolates the lowest set bit 4, which is bit 2 (numberOfTrailingZeros). Then m &= m - 1 removes it. |
| 3 | L3 | m=144 lowest=16 index=4 | m & -m isolates the lowest set bit 16, which is bit 4 (numberOfTrailingZeros). Then m &= m - 1 removes it. |
| 4 | L3 | m=128 lowest=128 index=7 | m & -m isolates the lowest set bit 128, which is bit 7 (numberOfTrailingZeros). Then m &= m - 1 removes it. |
| 5 | L5 | visits=3 | 3 iterations, one per set bit. |
State transitions
A mask can describe the state of a process: which items are already used, which switches are on, which cells are filled. A move that adds item i is mask | (1 << i), a move that toggles switch i is mask ^ (1 << i), and the goal state is a specific mask such as (1 << n) - 1.
Because there are at most 2^n states, a breadth-first search over masks, or a table indexed by mask, solves puzzles like lights-out and the shortest way to visit all items.
1// fewest moves to turn all n lights on, when each button toggles a set of lights2static int fewestPresses(int n, int[] buttonMask) {3int full = (1 << n) - 1;4int[] dist = new int[1 << n];5Arrays.fill(dist, -1);6dist[0] = 0;7Deque<Integer> queue = new ArrayDeque<>(List.of(0));8while (!queue.isEmpty()) {9int state = queue.poll();10if (state == full) return dist[state];11for (int b : buttonMask) {12int next = state ^ b; // press the button13if (dist[next] < 0) { dist[next] = dist[state] + 1; queue.add(next); }14}15}16return -1;17}
Bitmask DP introduction
Bitmask DP stores one value per subset: dp[mask] is the best answer for exactly the items in mask. You compute it from smaller masks by adding one item at a time. The table has 2^n entries and each transition tries n items, giving O(n · 2^n), far better than the n! of trying every order.
Classic examples are assigning n tasks to n people at minimum cost and the travelling salesman problem (O(n² · 2^n)). Dynamic programming itself is covered in Chapter 23; here is the shape.
1// minimum total cost to give each person i exactly one task, n <= 202static int assign(int[][] cost) {3int n = cost.length;4int[] dp = new int[1 << n];5Arrays.fill(dp, Integer.MAX_VALUE);6dp[0] = 0;7for (int mask = 0; mask < (1 << n); mask++) {8if (dp[mask] == Integer.MAX_VALUE) continue;9int person = Integer.bitCount(mask); // tasks used so far = next person10if (person == n) continue;11for (int task = 0; task < n; task++) {12if ((mask & (1 << task)) != 0) continue;13int next = mask | (1 << task);14dp[next] = Math.min(dp[next], dp[mask] + cost[person][task]);15}16}17return dp[(1 << n) - 1];18}
Worst
O(n · 2^n)
Space
O(2^n)
Practice ladder
Now try it yourself
Easy
- Generate all subsets of {1, 2, 3} with a bitmask
- Check whether set A is a subset of set B using masks
Medium
- Subsets of an array that sum to k
- Maximum product of word lengths with no common letters (26-bit masks)
- Count the subsets of a mask of size k
Advanced
- Assign tasks to people with bitmask DP
- Travelling salesman for n ≤ 15
- Count the pairs (sub, mask) with sub ⊆ mask over n bits and verify it is 3^n
Key takeaways
- A subset of up to 31 (or 63) items is one integer; set operations are single bit operations.
- Loop mask from 0 to 2^n - 1 for all subsets; use sub = (sub - 1) & mask for submasks.
- Bitmask DP keeps one value per subset: O(n · 2^n) instead of n!.