78. Subsets - Explanation

Problem Link

Description

Given an array nums of unique integers, return all possible subsets of nums.

The solution set must not contain duplicate subsets. You may return the solution in any order.

Example 1:

Input: nums = [1,2,3]

Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

Example 2:

Input: nums = [7]

Output: [[],[7]]

Constraints:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10


Topics

Recommended Time & Space Complexity

You should aim for a solution with O(n * (2^n)) time and O(n) space, where n is the size of the input array.


Hint 1

It is straightforward that if the array is empty we return an empty array. When we have an array [1] which is of size 1, we have two subsets, [[], [1]] as the output. Can you think why the output is so?


Hint 2

We can see that one subset includes a number, and another does not. From this, we can conclude that we need to find the subsets that include a number and those that do not. This results in 2^n subsets for an array of size n because there are many combinations for including and excluding the array of numbers. Since the elements are unique, duplicate subsets will not be formed if we ensure that we don't pick the element more than once in the current subset. Which algorithm is helpful to generate all subsets, and how would you implement it?


Hint 3

We can use backtracking to generate all possible subsets. We iterate through the given array with an index i and an initially empty temporary list representing the current subset. We recursively process each index, adding the corresponding element to the current subset and continuing, which results in a subset that includes that element. Alternatively, we skip the element by not adding it to the subset and proceed to the next index, forming a subset without including that element. What can be the base condition to end this recursion?


Hint 4

When the index i reaches the end of the array, we append a copy of the subset formed in that particular recursive path to the result list and return. All subsets of the given array are generated from these different recursive paths, which represent various combinations of "include" and "not include" steps for the elements of the array. As we are only iterating from left to right in the array, we don't pick an element more than once.


Company Tags


Prerequisites

Before attempting this problem, you should be comfortable with:

  • Recursion - Used to explore the decision tree where each element is either included or excluded
  • Backtracking - Needed to undo choices (remove elements) after exploring one branch of the decision tree
  • Bit Manipulation - Alternative approach uses bitmasks to represent subset membership

1. Backtracking

Intuition

The idea is to build all possible subsets by making a choice at each step:
for every number, we have two options — include it or exclude it.
This naturally forms a decision tree.

Backtracking helps us explore both choices:

  • Add the current number → explore further
  • Remove it (undo) → explore without it

Whenever we reach the end of the array, the current list represents one
complete subset, so we store it.

This systematically generates all 2ⁿ subsets.

Algorithm

  1. Maintain:
    • res → final list of all subsets
    • subset → current subset being built
  2. Define a recursive function dfs(i):
    • If i equals the length of the input:
      • Add a copy of subset to res
      • Return
    • Choice 1: include nums[i]
      • Append number to subset
      • Recurse to next index
      • Remove the number (backtrack)
    • Choice 2: skip nums[i]
      • Recurse to next index
  3. Start recursion with dfs(0)
  4. Return res
Example - Dry Run
Input: nums = [1, 2, 3]

Array:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
└───┴───┴───┘
  0   1   2


Backtracking Decision Tree:

                                    []
                         ┌──────────┴──────────┐
                    include 1             exclude 1
                         ↓                     ↓
                        [1]                   []
                   ┌─────┴─────┐         ┌─────┴─────┐
              include 2   exclude 2  include 2   exclude 2
                   ↓           ↓         ↓           ↓
                 [1,2]        [1]       [2]         []
                ┌──┴──┐     ┌──┴──┐   ┌──┴──┐     ┌──┴──┐
               +3   skip   +3   skip +3   skip   +3   skip
                ↓     ↓     ↓     ↓   ↓     ↓     ↓     ↓
            [1,2,3] [1,2] [1,3]  [1] [2,3] [2]   [3]   []


Step-by-step Subset Generation:


Step 1: dfs(0) - Consider nums[0] = 1
┌─────────────────────────────────────────────────────────────────────┐
│  Choice: Include 1                                                  │
│  subset = [1]                                                       │
│  Action: Recurse to dfs(1)                                          │
└─────────────────────────────────────────────────────────────────────┘


Step 2: dfs(1) - Consider nums[1] = 2  (subset = [1])
┌─────────────────────────────────────────────────────────────────────┐
│  Choice: Include 2                                                  │
│  subset = [1, 2]                                                    │
│  Action: Recurse to dfs(2)                                          │
└─────────────────────────────────────────────────────────────────────┘


Step 3: dfs(2) - Consider nums[2] = 3  (subset = [1, 2])
┌─────────────────────────────────────────────────────────────────────┐
│  Choice: Include 3                                                  │
│  subset = [1, 2, 3]                                                 │
│  Action: Recurse to dfs(3)                                          │
└─────────────────────────────────────────────────────────────────────┘


Step 4: dfs(3) - Base case reached (i >= len(nums))
┌─────────────────────────────────────────────────────────────────────┐
│  Add [1, 2, 3] to result                                            │
│  Result: [[1, 2, 3]]                                                │
│  Action: Return and backtrack                                       │
└─────────────────────────────────────────────────────────────────────┘


Step 5: Backtrack to dfs(2) - Exclude 3
┌─────────────────────────────────────────────────────────────────────┐
│  Remove 3 from subset                                               │
│  subset = [1, 2]                                                    │
│  Action: Recurse to dfs(3) without 3                                │
└─────────────────────────────────────────────────────────────────────┘


Step 6: dfs(3) - Base case reached
┌─────────────────────────────────────────────────────────────────────┐
│  Add [1, 2] to result                                               │
│  Result: [[1, 2, 3], [1, 2]]                                        │
│  Action: Return and backtrack                                       │
└─────────────────────────────────────────────────────────────────────┘


Step 7: Backtrack to dfs(1) - Exclude 2
┌─────────────────────────────────────────────────────────────────────┐
│  Remove 2 from subset                                               │
│  subset = [1]                                                       │
│  Action: Continue with nums[2] = 3                                  │
└─────────────────────────────────────────────────────────────────────┘


...continuing pattern for remaining branches...


Step 8-15: Process remaining combinations
┌─────────────────────────────────────────────────────────────────────┐
│  [1] + 3 → [1, 3] → add to result                                   │
│  [1] → add to result                                                │
│  [] + 2 → [2] + 3 → [2, 3] → add to result                          │
│  [2] → add to result                                                │
│  [] + 3 → [3] → add to result                                       │
│  [] → add to result                                                 │
└─────────────────────────────────────────────────────────────────────┘


Final Result:
┌───────────────────────────────────────────────────────────────────────────┐
│  [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]                    │
└───────────────────────────────────────────────────────────────────────────┘

Total subsets: 2^3 = 8
(order may vary based on implementation)

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = []
        subset = []

        def dfs(i):
            if i >= len(nums):
                res.append(subset.copy())
                return
            subset.append(nums[i])
            dfs(i + 1)
            subset.pop()
            dfs(i + 1)

        dfs(0)
        return res

Time & Space Complexity

  • Time complexity: O(n2n)O(n * 2 ^ n)
  • Space complexity:
    • O(n)O(n) extra space.
    • O(2n)O(2 ^ n) for the output list.

2. Iteration

Intuition

Start with just one subset: the empty set [].

For every number in the array, we take all the subsets we have so far and
create new subsets by adding the current number to each of them.

Example:

  • Start: [[]]
  • Add 1[[], [1]]
  • Add 2[[], [1], [2], [1,2]]
  • Add 3[[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]

Each step doubles the number of subsets.

Algorithm

  1. Initialize res = [[]] (start with empty subset).
  2. For each number num in the input array:
    • For every subset already in res:
      • Create a new subset that includes num
    • Append all these newly created subsets to res.
  3. Return res after processing all numbers.
Example - Dry Run
Input: nums = [1, 2, 3]

Array:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
└───┴───┴───┘
  0   1   2


Iterative Subset Building:

For each number, duplicate all existing subsets
and add the current number to each copy.


Initial State:
┌─────────────────────────────────────────────────────────────────────┐
│  res = [[]]                                                         │
│                                                                     │
│  Subsets so far:                                                    │
│  ┌────┐                                                             │
│  │ [] │                                                             │
│  └────┘                                                             │
└─────────────────────────────────────────────────────────────────────┘


Step 1: Process num = 1
┌─────────────────────────────────────────────────────────────────────┐
│  Current subsets: [[]]                                              │
│                                                                     │
│  For each existing subset, create a copy with 1 added:              │
│                                                                     │
│      []  ──────────────────►  [1]                                   │
│      (original)               (new copy + 1)                        │
│                                                                     │
│  New subsets created: [[1]]                                         │
│  Append to result                                                   │
└─────────────────────────────────────────────────────────────────────┘

res after Step 1:
┌────┬─────┐
│ [] │ [1] │
└────┴─────┘
  ↑      ↑
  │      └── newly added
  └── original


Step 2: Process num = 2
┌─────────────────────────────────────────────────────────────────────┐
│  Current subsets: [[], [1]]                                         │
│                                                                     │
│  For each existing subset, create a copy with 2 added:              │
│                                                                     │
│      []  ──────────────────►  [2]                                   │
│      [1] ──────────────────►  [1, 2]                                │
│      (originals)              (new copies + 2)                      │
│                                                                     │
│  New subsets created: [[2], [1, 2]]                                 │
│  Append to result                                                   │
└─────────────────────────────────────────────────────────────────────┘

res after Step 2:
┌────┬─────┬─────┬───────┐
│ [] │ [1] │ [2] │ [1,2] │
└────┴─────┴─────┴───────┘
  ↑     ↑     ↑      ↑
  └─────┴─────│──────│── original
              └──────┴── newly added


Step 3: Process num = 3
┌─────────────────────────────────────────────────────────────────────┐
│  Current subsets: [[], [1], [2], [1, 2]]                            │
│                                                                     │
│  For each existing subset, create a copy with 3 added:              │
│                                                                     │
│      []     ──────────────────►  [3]                                │
│      [1]    ──────────────────►  [1, 3]                             │
│      [2]    ──────────────────►  [2, 3]                             │
│      [1, 2] ──────────────────►  [1, 2, 3]                          │
│      (originals)                 (new copies + 3)                   │
│                                                                     │
│  New subsets created: [[3], [1, 3], [2, 3], [1, 2, 3]]              │
│  Append to result                                                   │
└─────────────────────────────────────────────────────────────────────┘

res after Step 3:
┌────┬─────┬─────┬───────┬─────┬───────┬───────┬─────────┐
│ [] │ [1] │ [2] │ [1,2] │ [3] │ [1,3] │ [2,3] │ [1,2,3] │
└────┴─────┴─────┴───────┴─────┴───────┴───────┴─────────┘


Growth Pattern:
┌────────────────────────────────────────────────────────────┐
│                                                            │
│     After processing:          # of subsets                │
│    ────────────────────────────────────────────            │
│     Initial (empty)             1   =  2^0                 │
│     After num = 1               2   =  2^1                 │
│     After num = 2               4   =  2^2                 │
│     After num = 3               8   =  2^3                 │
│                                                            │
│     Pattern: Each step doubles the number of subsets!      │
│                                                            │
└────────────────────────────────────────────────────────────┘


Final Result:
┌───────────────────────────────────────────────────────────────────────────┐
│  [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]                    │
└───────────────────────────────────────────────────────────────────────────┘

Total subsets: 2^3 = 8

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        res = [[]]

        for num in nums:
            res += [subset + [num] for subset in res]

        return res

Time & Space Complexity

  • Time complexity: O(n2n)O(n * 2 ^ n)
  • Space complexity:
    • O(n)O(n) extra space.
    • O(2n)O(2 ^ n) for the output list.

3. Bit Manipulation

Intuition

Every subset can be represented using bits.

For an array of length n, there are 2^n possible subsets.
Each subset corresponds to a number from 0 to 2^n - 1.

Example for nums = [a, b, c]:

  • 000 → choose nothing → []
  • 001 → choose c
  • 010 → choose b
  • 011 → choose b, c
  • 100 → choose a
  • ...and so on.

Each bit tells us whether to include the corresponding element.

So for every integer i from 0 to (1 << n) - 1:

  • Check each bit j of i
  • If bit j is 1, include nums[j] in the current subset.

Algorithm

  1. Let n be the length of nums.
  2. Loop i from 0 to (1 << n) - 1 (this generates all bitmasks).
  3. For each i, build a subset:
    • For each position j from 0 to n - 1:
      • If the j-th bit of i is set, include nums[j] in the subset.
  4. Add the subset to the result list.
  5. Return all subsets.
Example - Dry Run
Input: nums = [1, 2, 3]

Array:
┌───┬───┬───┐
│ 1 │ 2 │ 3 │
└───┴───┴───┘
  0   1   2

n = 3, so we iterate i from 0 to 7 (2^3 - 1)


Bitmask to Subset Mapping:

┌─────────────────────────────────────────────────────────────────────┐
│  Each bit position corresponds to an element in nums:               │
│                                                                     │
│      Bit Position:     2         1         0                        │
│      Array Element:  nums[2]   nums[1]   nums[0]                    │
│      Value:            3         2         1                        │
│                                                                     │
│  If bit j is SET (1) → include nums[j] in subset                    │
│  If bit j is CLEAR (0) → exclude nums[j] from subset                │
└─────────────────────────────────────────────────────────────────────┘


Step-by-step Iteration:


i = 0  (binary: 000)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 0 │ 0 │ 0 │                                        │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     -   -   -                                          │
│                                                                     │
│     Subset: []                                                      │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 1  (binary: 001)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 0 │ 0 │ 1 │  ← bit 0 is SET                        │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     -   -   *                                          │
│                                                                     │
│     Subset: [1]                                                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 2  (binary: 010)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 0 │ 1 │ 0 │  ← bit 1 is SET                        │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     -   *   -                                          │
│                                                                     │
│     Subset: [2]                                                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 3  (binary: 011)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 0 │ 1 │ 1 │  ← bits 0,1 are SET                    │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     -   *   *                                          │
│                                                                     │
│     Subset: [1, 2]                                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 4  (binary: 100)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 1 │ 0 │ 0 │  ← bit 2 is SET                        │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     *   -   -                                          │
│                                                                     │
│     Subset: [3]                                                     │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 5  (binary: 101)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 1 │ 0 │ 1 │  ← bits 0,2 are SET                    │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     *   -   *                                          │
│                                                                     │
│     Subset: [1, 3]                                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 6  (binary: 110)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 1 │ 1 │ 0 │  ← bits 1,2 are SET                    │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     *   *   -                                          │
│                                                                     │
│     Subset: [2, 3]                                                  │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


i = 7  (binary: 111)
┌─────────────────────────────────────────────────────────────────────┐
│                                                                     │
│     Bits:      ┌───┬───┬───┐                                        │
│                │ 1 │ 1 │ 1 │  ← all bits SET                        │
│                └───┴───┴───┘                                        │
│     Position:    2   1   0                                          │
│     nums[j]:     3   2   1                                          │
│     Include:     *   *   *                                          │
│                                                                     │
│     Subset: [1, 2, 3]                                               │
│                                                                     │
└─────────────────────────────────────────────────────────────────────┘


Summary Table:
┌───────┬──────────┬─────────────────┬─────────────────────────────────┐
│   i   │  Binary  │     Subset      │           Explanation           │
├───────┼──────────┼─────────────────┼─────────────────────────────────┤
│   0   │   000    │       []        │  No bits set → empty subset     │
│   1   │   001    │       [1]       │  Bit 0 set → include nums[0]    │
│   2   │   010    │       [2]       │  Bit 1 set → include nums[1]    │
│   3   │   011    │      [1,2]      │  Bits 0,1 set → include both    │
│   4   │   100    │       [3]       │  Bit 2 set → include nums[2]    │
│   5   │   101    │      [1,3]      │  Bits 0,2 set → include both    │
│   6   │   110    │      [2,3]      │  Bits 1,2 set → include both    │
│   7   │   111    │     [1,2,3]     │  All bits set → include all     │
└───────┴──────────┴─────────────────┴─────────────────────────────────┘


Final Result:
┌───────────────────────────────────────────────────────────────────────────┐
│  [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]                    │
└───────────────────────────────────────────────────────────────────────────┘

Total subsets: 2^3 = 8

class Solution:
    def subsets(self, nums: List[int]) -> List[List[int]]:
        n = len(nums)
        res = []
        for i in range(1 << n):
            subset = [nums[j] for j in range(n) if (i & (1 << j))]
            res.append(subset)
        return res

Time & Space Complexity

  • Time complexity: O(n2n)O(n * 2 ^ n)
  • Space complexity:
    • O(n)O(n) extra space.
    • O(2n)O(2 ^ n) for the output list.

Common Pitfalls

Modifying the Subset After Adding to Result

When adding a subset to the result list, you must add a copy of the current subset. Otherwise, backtracking modifications will alter subsets already in the result.

# Wrong: adds reference that gets modified later
res.append(subset)
# Correct: add a copy
res.append(subset[:])  # or list(subset)

Forgetting the Empty Subset

The power set always includes the empty subset []. Forgetting to initialize with an empty subset or starting the iteration incorrectly will miss this case.

Incorrect Index Handling in Backtracking

When recursing, start from i + 1 to avoid reusing the same element and to prevent duplicate subsets. Starting from 0 or i generates incorrect results.

# Wrong: includes current element again
for j in range(i, len(nums)):
# Correct: start from next element
for j in range(i + 1, len(nums)):

Integer Overflow in Bitmask Approach

For the bitmask solution, 1 << n can overflow for large n. In most languages, this limits the approach to around 30 elements, though problem constraints usually keep n small.