Bitmasking and Subset Enumeration
Bitmasking & Subset Enumeration
Every one of the earlier pages treated an integer as a number. This page flips the lens: treat an
integer as a set. If you have at most ~20 elements, the bits of a single int can encode
which of them you have chosen — bit i is 1 exactly when element
i is in the set. That one idea turns “loop over all subsets” into “count from
0 to 2n − 1,” and it is the machinery underneath the
Subsets problem, Maximum-XOR, and the whole family of bitmask DP problems (travelling salesman,
assignment, Hamiltonian paths). Get this page solid and those problems stop being magic.
An int is a set
Give each element an index 0..n−1. A subset is then an n-bit number
(a mask): element i is present iff bit i is set. With three elements
{a, b, c} at indices 0, 1, 2:
| mask (binary) | mask (decimal) | bits set | subset |
|---|---|---|---|
000 | 0 | — | {} |
001 | 1 | bit 0 | {a} |
010 | 2 | bit 1 | {b} |
011 | 3 | bits 0,1 | {a, b} |
100 | 4 | bit 2 | {c} |
101 | 5 | bits 0,2 | {a, c} |
110 | 6 | bits 1,2 | {b, c} |
111 | 7 | bits 0,1,2 | {a, b, c} |
Set operations become bit operations, and this dictionary is worth memorizing because it is why bitmask DP is fast — a whole set fits in one register and unions/intersections are single instructions:
- Union
A ∪ B→a | b - Intersection
A ∩ B→a & b - Membership “is
iin the set?” →(mask >> i) & 1 - Add element
i→mask | (1 << i) - Remove element
i→mask & ~(1 << i) - Size
|A|→ popcount(mask) (Integer.bitCount/bits.OnesCount) - Complement (within
nelements) →mask ^ ((1 << n) − 1)
Enumerating all 2n subsets — just count
Because a subset is an n-bit number, and the n-bit numbers are exactly
0, 1, 2, …, 2n−1, iterating every integer in that range visits every
subset exactly once — no recursion, no bookkeeping. For each mask, read off which bits are
set to recover the subset. The traced table above is that loop for n = 3: the decimal
column runs 0..7 and each row is one subset.
Why exactly once and no duplicates? A subset corresponds to one and only one mask (a set of chosen indices
maps bijectively to a bit pattern), and the loop hits each integer in [0, 2n) a
single time. Bijection + one visit each = every subset produced exactly once.
Java: enumerate every subset
import java.util.*;
public class BitmaskSubsets {
// All 2^n subsets of items[0..n-1], read straight off the bits.
static List<List<Integer>> allSubsets(int[] items) {
int n = items.length;
List<List<Integer>> out = new ArrayList<>();
for (int mask = 0; mask < (1 << n); mask++) { // 0 .. 2^n - 1
List<Integer> subset = new ArrayList<>();
for (int i = 0; i < n; i++) {
if ((mask & (1 << i)) != 0) { // is element i chosen?
subset.add(items[i]);
}
}
out.add(subset);
}
return out;
}
public static void main(String[] args) {
System.out.println(allSubsets(new int[]{1, 2, 3}));
// [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
}
}Go: enumerate every subset
package main
import "fmt"
// allSubsets returns all 2^n subsets of items, read straight off the bits.
func allSubsets(items []int) [][]int {
n := len(items)
var out [][]int
for mask := 0; mask < (1 << n); mask++ { // 0 .. 2^n - 1
var subset []int
for i := 0; i < n; i++ {
if mask&(1<<i) != 0 { // is element i chosen?
subset = append(subset, items[i])
}
}
out = append(out, subset)
}
return out
}
func main() {
fmt.Println(allSubsets([]int{1, 2, 3}))
// [[] [1] [2] [1 2] [3] [1 3] [2 3] [1 2 3]]
}Iterating only the set bits (the elements you actually have)
The inner loop above walks all n positions even for a nearly-empty mask. When you want to
touch only the chosen elements, use the lowest-set-bit trick from the tricks page: repeatedly read the
lowest set bit and clear it. Integer.numberOfTrailingZeros (Go: bits.TrailingZeros)
turns the isolated low bit into its index.
for (int m = mask; m != 0; m &= (m - 1)) { // m & (m-1) clears the lowest set bit
int i = Integer.numberOfTrailingZeros(m); // index of that lowest set bit
// ... use element i ...
}
This runs popcount(mask) times, not n times — the same Kernighan idea that
made popcount data-dependent.
Submask enumeration — every subset of a chosen set
A subtler need: given a fixed mask, walk every submask of it — every set whose
chosen bits are a subset of mask’s bits (formally, every s with
s & mask == s). This is the core loop of bitmask DP where the state is “this group of
items” and you must split it into a sub-group and its complement. The idiom is famously terse:
for (int s = mask; ; s = (s - 1) & mask) {
process(s); // s ranges over every submask, largest first
if (s == 0) break; // 0 (the empty submask) is the last one
}
Why s = (s - 1) & mask lands on the next submask down. Take the current submask
s. Subtracting 1 flips s’s lowest set bit to 0 and turns every
bit below it into 1 (a borrow, exactly as on the popcount page). Those newly-1 low
bits may include positions that are not in mask — garbage we must not keep —
so we & mask to snap back onto mask’s bits. The result is provably the
largest submask strictly smaller than s: it clears one of s’s bits and
refills with the highest-value mask-bits available below it. Because each step strictly
decreases s yet stays a submask, the loop marches through all submasks in decreasing order
and terminates at 0.
Traced for mask = 1011 (bits 0, 1, 3 set; it has 23 = 8 submasks):
| s (binary) | s (dec) | s − 1 (binary) | (s−1) & 1011 → next |
|---|---|---|---|
1011 | 11 | 1010 | 1010 (10) |
1010 | 10 | 1001 | 1001 (9) |
1001 | 9 | 1000 | 1000 (8) |
1000 | 8 | 0111 | 0011 (3) |
0011 | 3 | 0010 | 0010 (2) |
0010 | 2 | 0001 | 0001 (1) |
0001 | 1 | 0000 | 0000 (0) |
0000 | 0 | s == 0 → stop | |
The visited set {11, 10, 9, 8, 3, 2, 1, 0} is exactly the eight subsets of {0, 1, 3}
— note it correctly skips masks like 0100 (4) and 0101 (5) that use
bit 2, which is not in 1011.
Java: enumerate submasks
public class Submasks {
static void submasks(int mask) {
for (int s = mask; ; s = (s - 1) & mask) {
System.out.print(s + " ");
if (s == 0) break; // include the empty submask, then stop
}
System.out.println();
}
public static void main(String[] args) {
submasks(0b1011); // 11 10 9 8 3 2 1 0
}
}Go: enumerate submasks
package main
import "fmt"
func submasks(mask int) {
for s := mask; ; s = (s - 1) & mask {
fmt.Print(s, " ")
if s == 0 { // include the empty submask, then stop
break
}
}
fmt.Println()
}
func main() {
submasks(0b1011) // 11 10 9 8 3 2 1 0
}The 3n that surprises people
If you nest the submask loop inside a loop over all masks — the standard shape of subset-sum
DP — the total work is not 2n × 2n. It is 3n,
and the proof is a one-liner once you see it. Sum over every mask of its submask count. Look at
one bit position across the (mask, submask) pair: it is in exactly one of three states — not in
mask; in mask and in the submask; or in mask but not in the submask.
Three independent choices per bit, n bits → 3n total (mask, submask)
pairs. That is why “iterate all subsets, and for each all its subsets” is Θ(3n),
not Θ(4n) — a fact interviewers love to probe.
The bridge to bitmask DP
Everything above is scaffolding for one pattern: let the DP state be a set, encoded as a mask. When
n ≤ ~20, there are only 2n possible states, which is tractable, so a
problem that is exponential in the naive framing (“try all orderings/assignments”) becomes a table
indexed by mask. Two canonical shapes:
- Assignment / set-cover:
dp[mask]= best cost to cover / assign the elements inmask. Fill masks in increasing order; each transition adds one element (mask | (1 << i)). ComplexityO(2n · n). - Travelling salesman / Hamiltonian path:
dp[mask][i]= cheapest route visiting exactly the cities inmaskand ending ati. ComplexityO(2n · n2)— the reason TSP is “solvable” fornup to ~18–20 but not 100.
The neutral recurrence for the assignment shape (pseudocode — the mask arithmetic is exactly what this page taught):
dp[0] = 0
for mask in 1 .. (1<<n)-1:
i = index of the next element to place // e.g. popcount(mask)-1, or lowest unset bit
dp[mask] = min over j in mask of
dp[mask without j] + cost(place element -> slot for j)
When bitmask enumeration is the right tool — and when it is not
- Use it when
nis small.220 ≈ 106and222 ≈ 4 × 106are fine; a per-mask× nfactor pushes the practical ceiling to aboutn ≤ 20–22. Beyond ~25 the2nterm alone is hundreds of millions and it stops being viable. - Use it when you need a stable id per subset — the mask is an integer key, perfect for memoization, DP tables, or deduping subsets in a hash set. Backtracking gives you the subset but not a cheap canonical id.
- Do not use it when
nis large.2nis exponential; no bit trick rescuesn = 40. If the structure allows, switch to meet-in-the-middle (split into two halves ofn/2, enumerate2n/2each, combine) — that stretches the ceiling ton ≈ 40. For truly largenyou need a polynomial algorithm (greedy / flow / DP-not-over-subsets) instead. - Do not use it when you can prune. Straight enumeration always produces all
2nmasks. If the problem has constraints that let you abandon most branches early (“subsets summing to exactlyT”), backtracking with pruning can explore far fewer than2nnodes — the subject of the Subsets problem page.
Traps
1 << noverflows pastn = 30in a 32-bitint. Forn ≥ 31use1L << nin Java / a wider type; but at that size2nis infeasible anyway, which is the real signal.- Shift amount must be less than the type width.
1 << 32is undefined-ish in Java (the shift count is taken mod 32, so it yields1, not0) — a silent, vicious bug. Keepnstrictly within range. - Membership test parenthesization — a Java trap, not a Go one. In Java,
&binds looser than!=, somask & (1 << i) != 0parses asmask & ((1<<i) != 0)— which does not even compile against anint. Always write(mask & (1 << i)) != 0. In Go the precedence is the reverse (&is a multiplicative-level operator, tighter than!=), so the unparenthesized form actually compiles and is correct — but parenthesize anyway so the intent is portable and unmistakable.
Takeaways
- An
intis a set of up to ~20–30 elements; bitimeans “elementiis in.” Union/intersection/membership are single bit ops. - All
2nsubsets = the integers0..2n−1; the bijection guarantees each subset once, no recursion needed. s = (s-1) & maskwalks every submask in decreasing order — a borrow that clears one bit, snapped back ontomask; nesting it over all masks isΘ(3n).- This is the foundation of bitmask DP: state = mask, tractable only while
nstays small.
First-principles treatment of set/int duality, the submask-enumeration invariant, the
3n counting argument, and the bitmask-DP bridge; both samples were compiled and run to
confirm the stated output.
🤖 Don't fully get this? Learn it with Claude
Stuck on Bitmasking and Subset Enumeration? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.
Build the mental picture, not memorization.
I just read a lesson on **Bitmasking and Subset Enumeration** (DSA) and want to truly understand it. Explain Bitmasking and Subset Enumeration from first principles using ONE vivid real-world analogy and a visual mental model — draw it as ASCII art or a clear step-by-step diagram — with a concrete example using real numbers. Then ask me one question to check I got the mental picture, and wait for my reply. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Socratic — adapts to where you're stuck.
Teach me **Bitmasking and Subset Enumeration** interactively. Ask me ONE guiding question at a time, wait for my answer, and adapt to my confusion — build the idea with me step by step instead of explaining it all at once. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Active recall exposes what you missed.
Quiz me on **Bitmasking and Subset Enumeration** with 5 questions, easy to tricky, ONE at a time. Tell me if each answer is right; at the end, explain clearly what I got wrong and why. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
Intuition + hook + flashcards for long-term memory.
Help me remember **Bitmasking and Subset Enumeration** for the long term: give the one-sentence intuition, a memorable hook/mnemonic, a tiny worked example, and 3 active-recall flashcards (Q -> A). If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.