CMD Guide
HomeDSABit Manipulation

medium Subsets

Subsets via Bitmask

Problem. Given an array nums of distinct integers, return the power set — every possible subset, including the empty set and the full set. For nums = [1, 2, 3] there are 23 = 8 subsets. Order of the subsets in the output does not matter. (LeetCode 78.)

This is the direct payoff of the bitmasking page: a subset is an n-bit number, so generating the power set is nothing more than counting from 0 to 2n−1 and reading each integer’s bits. But the interviewer’s real question is almost never “can you produce subsets” — it is “bitmask or backtracking, and why?” That judgment is the substance of this page.

The naive baseline — and why it is not really “naive”

The recursive/backtracking construction is the textbook first answer: at each element decide take it or skip it, recursing down a binary decision tree of depth n. Every node of that tree is a valid subset, so you snapshot the current path at every node. It is O(n · 2n) time (there are 2n subsets and copying each costs up to O(n)) and uses O(n) recursion stack. That is already optimal in time — you cannot beat the output size. So the bitmask version is not asymptotically faster; it is a different shape with different trade-offs, which is exactly why the choice is interesting.

The bit insight

Bijection: each subset ↔ one n-bit mask, element i present iff bit i is set. The masks 0..2n−1 are precisely the n-bit patterns, so looping that range emits every subset exactly once — no recursion, no explicit “undo,” no stack. The outer loop picks the mask; an inner loop over the n bit positions reads which elements the mask selects.

Traced example — nums = [1, 2, 3]

Index the array so nums[0]=1, nums[1]=2, nums[2]=3. Bit i controls nums[i]:

maskbinary (b2 b1 b0)bits set → indicessubset emitted
0000[]
10010[1]
20101[2]
30110,1[1, 2]
41002[3]
51010,2[1, 3]
61101,2[2, 3]
71110,1,2[1, 2, 3]

Eight masks, eight subsets, output [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]. Notice the emission order differs from backtracking’s (which yields [], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]) — both are correct power sets; the problem does not constrain order.

Java: bitmask power set

import java.util.*;

public class Subsets {
    public List<List<Integer>> subsets(int[] nums) {
        int n = nums.length;
        List<List<Integer>> result = new ArrayList<>();
        for (int mask = 0; mask < (1 << n); mask++) {   // 2^n masks
            List<Integer> subset = new ArrayList<>();
            for (int i = 0; i < n; i++) {
                if ((mask & (1 << i)) != 0) {           // bit i set -> take nums[i]
                    subset.add(nums[i]);
                }
            }
            result.add(subset);
        }
        return result;
    }

    public static void main(String[] args) {
        System.out.println(new Subsets().subsets(new int[]{1, 2, 3}));
        // [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
    }
}

Go: bitmask power set

package main

import "fmt"

func subsets(nums []int) [][]int {
	n := len(nums)
	var result [][]int
	for mask := 0; mask < (1 << n); mask++ { // 2^n masks
		var subset []int
		for i := 0; i < n; i++ {
			if mask&(1<<i) != 0 { // bit i set -> take nums[i]
				subset = append(subset, nums[i])
			}
		}
		result = append(result, subset)
	}
	return result
}

func main() {
	fmt.Println(subsets([]int{1, 2, 3}))
	// [[] [1] [2] [1 2] [3] [1 3] [2 3] [1 2 3]]
}

The named alternative — backtracking

Worth writing out, because the judgment call is only meaningful if you can produce both. Every recursion node is emitted as a subset; the for loop chooses the next element to append, recurses, then undoes the choice so the next iteration starts clean.

Java: backtracking power set

import java.util.*;

public class SubsetsBacktrack {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        backtrack(nums, 0, new ArrayList<>(), result);
        return result;
    }

    private void backtrack(int[] nums, int start,
                           List<Integer> cur, List<List<Integer>> result) {
        result.add(new ArrayList<>(cur));        // snapshot: every node is a subset
        for (int i = start; i < nums.length; i++) {
            cur.add(nums[i]);                    // choose
            backtrack(nums, i + 1, cur, result); // explore
            cur.remove(cur.size() - 1);          // un-choose (backtrack)
        }
    }

    public static void main(String[] args) {
        System.out.println(new SubsetsBacktrack().subsets(new int[]{1, 2, 3}));
        // [[], [1], [1, 2], [1, 2, 3], [1, 3], [2], [2, 3], [3]]
    }
}

Go: backtracking power set

package main

import "fmt"

func subsets(nums []int) [][]int {
	var result [][]int
	var cur []int
	var backtrack func(start int)
	backtrack = func(start int) {
		// snapshot: MUST copy, cur's backing array is mutated in place
		subset := make([]int, len(cur))
		copy(subset, cur)
		result = append(result, subset)
		for i := start; i < len(nums); i++ {
			cur = append(cur, nums[i]) // choose
			backtrack(i + 1)           // explore
			cur = cur[:len(cur)-1]     // un-choose
		}
	}
	backtrack(0)
	return result
}

func main() {
	fmt.Println(subsets([]int{1, 2, 3}))
	// [[] [1] [1 2] [1 2 3] [1 3] [2] [2 3] [3]]
}

Complexity — both are output-optimal

Judgment layer — when to reach for the bitmask, and when not

Edge cases & traps

Takeaways


Standard power-set formulation (LeetCode 78) re-derived from the set/int bijection, paired against the backtracking alternative; all four samples were compiled and run to confirm the emitted subsets.

🧩 Pattern · Bit Manipulation

Recognize it: Presence / pairing / parity with XOR and masks → think in bits.

→ more Bit Manipulation problems, grouped
🤖 Don't fully get this? Learn it with Claude

Stuck on Subsets? Open Claude, copy a block below, and it'll teach you this exact concept — visually and interactively.

🪜 Hint ladder (no spoilers)

Progressively stronger hints — you still solve it.

I'm working on the problem **Subsets** (DSA). Give me a HINT LADDER: start with the tiniest nudge, then wait. Only reveal the next, stronger hint when I ask. Do NOT show the full solution unless I type 'show solution'. Keep me doing the thinking. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🎨 Explain the approach visually

See the technique, not just code.

Explain the optimal approach to **Subsets** with a VISUAL walkthrough: trace it on a small concrete example using ASCII art / a step-by-step diagram, narrate what changes each step, then give time & space complexity with a one-line derivation. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔍 Review my solution

Catch bugs, edge cases, sub-optimality.

I'll paste my solution to **Subsets**. Review it for correctness, missed edge cases, and time/space complexity, then coach me toward the optimal — don't just rewrite it. Ask me to paste my code now. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.
🔁 Drill the pattern

Lock in recognition with look-alikes.

Give me 2 problems that use the SAME underlying pattern as **Subsets**. For each, let me attempt first, then review my answer and name the trigger signal that reveals the pattern. If you're unsure or a claim isn't standard, say so and reason from first principles instead of guessing.

📝 My notes