CMD Guide
HomeDSABit Manipulation

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 setsubset
0000{}
0011bit 0{a}
0102bit 1{b}
0113bits 0,1{a, b}
1004bit 2{c}
1015bits 0,2{a, c}
1106bits 1,2{b, c}
1117bits 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:

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
10111110101010 (10)
10101010011001 (9)
1001910001000 (8)
1000801110011 (3)
0011300100010 (2)
0010200010001 (1)
0001100000000 (0)
00000s == 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:

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

Traps

Takeaways


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.

🎨 Explain it visually

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.
🤔 Walk me through it (interactive)

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.
🧪 Quiz me & fix my gaps

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.
🧠 Make it stick

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.

📝 My notes