Subsets (Backtracking)

Interview Prep

StandardBacktrackingbacktrackingrecursion

The problem

Given an array of distinct integers, return all 2n possible subsets, in any order.

Pattern: backtracking template

Subsets is the canonical backtracking warm-up. The decision at each step is "should this element be included in the current subset?". The skeleton — append to path, recurse, pop — is the same skeleton you'll use for permutations, combinations, combination sum, n-queens, sudoku, word search, and dozens more. Get this one into your fingers.

Every node in the recursion tree is a valid subset (we append(path.copy()) on entry, not at leaves). This is what makes Subsets different from Permutations or Combination Sum, where we only emit at the leaf.

Backtracking solution

def subsets(nums: list[int]) -> list[list[int]]:
    out, path = [], []
    def back(i: int):
        out.append(path.copy())          # every prefix-of-decisions is itself a valid subset
        for j in range(i, len(nums)):
            path.append(nums[j])
            back(j + 1)
            path.pop()                   # undo for the next branch
    back(0)
    return out

Trace

nums = [1, 2, 3]

back(0, path=[]):    out += [[]]
  j=0: path=[1], back(1):    out += [[1]]
    j=1: path=[1,2], back(2):    out += [[1,2]]
      j=2: path=[1,2,3], back(3):    out += [[1,2,3]]. no children.
      pop -> [1,2]
    pop -> [1]
    j=2: path=[1,3], back(3):    out += [[1,3]]. no children.
    pop -> [1]
  pop -> []
  j=1: path=[2], back(2):    out += [[2]]
    j=2: path=[2,3], back(3):    out += [[2,3]]. no children.
    pop -> [2]
  pop -> []
  j=2: path=[3], back(3):    out += [[3]]. no children.
  pop -> []

result: [[], [1], [1,2], [1,2,3], [1,3], [2], [2,3], [3]]

Alternative: bitmask enumeration

For small n (≤ ~20), every subset corresponds to an n-bit mask. Iterating 0..2^n − 1 and selecting the elements whose bits are set gives the same answer in one pass. Often the cleanest code; doesn't generalize to "decisions with more than two choices".

def subsets_bitmask(nums):
    """For small n (~20), iterate all 2^n bitmasks."""
    n = len(nums)
    return [
        [nums[i] for i in range(n) if mask & (1 << i)]
        for mask in range(1 << n)
    ]

Alternative: iterative doubling

The subsets of nums + [x] are exactly the subsets of nums, plus each of those with x appended. Start with [[]] and grow.

def subsets_iterative(nums):
    """Build by doubling: subsets(nums + [x]) = subsets(nums) ∪ {s+[x] for s in subsets(nums)}."""
    out = [[]]
    for x in nums:
        out += [s + [x] for s in out]
    return out

Complexity

Variations worth knowing