LeetCode #78 Easy

Subsets (Power Set)

Return all subsets (the power set) of an array of distinct integers — 2ⁿ of them.

backtrackingbit-manipulation
Open on LeetCode ↗
02

Intuition

Every subset is a chain of n independent yes/no decisions: include this element or not. Backtracking walks that decision tree — take the element, recurse, un-take it, recurse — and every leaf is one subset. Equivalently, each n-bit number 0..2ⁿ−1 is a subset: bit i set means take nums[i].

How to spot this pattern

The power set as a prefix tree: every node in the recursion is a subset, so you record on entry rather than only at the leaves. The j + 1 in the recursive call is what keeps combinations from becoming permutations. Recognise the family — subsets, subsets II, combinations — by whether elements are chosen without regard to order.

03

Approach

1

Include / exclude recursion

dfs(i, path): at each index, branch on taking nums[i] or skipping it; at i == n, snapshot path. 2ⁿ leaves = 2ⁿ subsets.

2

Snapshot every node instead

The common variant records path on entry and only branches on which later element to add next — same output, no explicit base case.

3

Bitmask alternative

for mask in range(2**n): build the subset from set bits. Iterative, no recursion, same O(n·2ⁿ).

04

Solution & live demo

1class Solution:
2 def subsets(self, nums):
3 res, path = [], []
4 def dfs(i):
5 res.append(path[:]) # snapshot current choices
6 for j in range(i, len(nums)):
7 path.append(nums[j]) # choose
8 dfs(j + 1)
9 path.pop() # un-choose
10 dfs(0)
11 return res
05

Common pitfalls

Only recording at the leaves

✗ Wrong
def dfs(i):
    if i == len(nums):
        res.append(path[:])
        return
✓ Right
def dfs(i):
    res.append(path[:])
    for j in range(i, len(nums)):
        ...

Every partial path is itself a valid subset, so the answer lives at every node of the tree, not just the bottom. Recording only at leaves returns a fraction of the 2^n subsets.

Recursing with j instead of j + 1

✗ Wrong
dfs(j)
✓ Right
dfs(j + 1)

Passing j lets the same element be chosen again, generating multisets like [1, 1] from a single 1. Advancing past it enforces that each element is considered exactly once per path.

Appending path by reference

✗ Wrong
res.append(path)
✓ Right
res.append(path[:])

path is mutated throughout the search, so every stored reference ends up showing the same final state — an empty list. The snapshot has to be a copy.

06

Edge cases

Empty input

Power set is [[]] — one empty subset.

Result must contain [] and the full set

The all-skip and all-take branches produce them.

path must be copied

Append path[:], not path — the list mutates during backtracking.

07

Complexity

Time
O(n · 2ⁿ)
Space
O(n)
2ⁿ subsets, each up to n long; recursion depth n.