Example 1
ready- Input
nums = [ 1, 2, 3 ]- Expected
[ [], [ 1 ], [ 2 ], [ 3 ], [ 1, 2 ], [ 1, 3 ], [ 2, 3 ], [ 1, 2, 3 ] ]- Why
- three independent include-or-skip choices create 2³ = 8 subsets
Picking toppings for a pizza creates more possibilities than it first appears: every topping can be included or left out. An array gives you the same branching choice.
nums holds distinct integers. Build its power set — the collection of every
possible subset, where a subset selects zero or more input values.
A few ground rules:
[] and the subset containing every value.1 <= nums.length <= 12-100 <= nums[i] <= 100nums are distinct.Hints
Each value has two futures: take it or leave it. With n values, how many complete
chains of those decisions exist?
Sort a copy of nums, then carry one growing list through the recursion. At each
index, explore the branch that includes the value and the branch that skips it.
After exploring the include branch, remove its last value before you explore the skip branch. That one undo keeps the two branches independent.
Could you produce the subsets one at a time, without keeping the full power set in memory, while preserving ascending order inside each subset?
Visible cases
nums = [
1,
2,
3
][
[],
[
1
],
[
2
],
[
3
],
[
1,
2
],
[
1,
3
],
[
2,
3
],
[
1,
2,
3
]
]nums = [
4
][
[],
[
4
]
]nums = [
3,
-1
][
[],
[
-1
],
[
3
],
[
-1,
3
]
]Interview signal
The output already contains 2^n subsets, so no algorithm can dodge exponential work.
The useful question is how cleanly you visit every answer exactly once.
A bitmask is an integer whose binary digits act like yes/no switches. Bit i tells
you whether to include values[i]. The integers from 0 through 2^n - 1 cover every
possible switch setting.
def subsets(nums: list[int]) -> list[list[int]]:
values = sorted(nums)
answer: list[list[int]] = []
for mask in range(1 << len(values)):
subset: list[int] = []
for i, value in enumerate(values):
if mask & (1 << i):
subset.append(value)
answer.append(subset)
return answerSorting first makes every emitted subset ascending. Different masks differ at some bit, so they can't describe the same subset when the input values are distinct.
Time O(n · 2^n). Space O(n · 2^n) for the returned power set; the temporary subset uses O(n) additional space.
Backtracking is depth-first search that chooses a value, explores that choice, and then un-chooses it before trying another path. Here, each index creates two branches: include the value or skip it. Those branches form a solution-space tree, a tree whose nodes represent partial subsets. That choose/explore/un-choose cycle is the backtracking pattern.
def subsets(nums: list[int]) -> list[list[int]]:
values = sorted(nums)
answer: list[list[int]] = []
path: list[int] = []
def search(index: int) -> None:
if index == len(values):
answer.append(path.copy())
return
path.append(values[index]) # choose
search(index + 1) # explore
path.pop() # un-choose
search(index + 1) # explore the skip branch
search(0)
return answerEvery root-to-leaf path makes one include-or-skip decision for every value, so it maps to exactly one subset. Conversely, every subset supplies one such decision path. That gives both completeness and uniqueness.
Time O(n · 2^n). Space O(n · 2^n) for the output, with O(n) auxiliary recursion and path space.
Ready to run.
3 cases are queued.
Visible testcase
Case 1
Expected
[
[],
[
1
],
[
2
],
[
3
],
[
1,
2
],
[
1,
3
],
[
2,
3
],
[
1,
2,
3
]
]