Subsets

Master the art of backtracking through power set generation

MEDIUM

Problem Statement

Challenge

Given an array numsArray of unique integers of unique integers, return all possible subsets (the power set). The solution set must not contain duplicate subsets. You may return the solution in any order.

Example 1

Input/Output
Input: nums = [1,2,3]

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

Example 2

Input/Output
Input: nums = [7]

Output: [[],[7]]
šŸ’”
Key Insight: For an array of length n, there are exactly 2n possible subsets. Each element can either be included or excluded.
O(n·2ⁿ)
Time Complexity
O(n)
Space Complexity
2ⁿ
Total Subsets

Core Concepts

What is Backtracking?

Backtracking is an algorithmic technique for solving problems recursively by trying to build a solution incrementally, abandoning solutions ("backtracking") as soon as it determines that the solution cannot be valid.

Core Principles

  • Make a choice (include or exclude element)
  • Explore the consequences
  • Undo the choice if needed (backtrack)
  • Try the alternative choice
āš ļø
Remember: Always copy the subset when adding to results! Otherwise, backtracking will modify subsets already in the result list.

Decision Tree Visualization

Each level represents a decision point for one element. Left branch = include, Right branch = exclude.

                    []
                   /  \
                 [1]  []
                /  \   /  \
              [1,2]  [1]  [2]  []
              /  \   /  \  /  \  /  \
          [1,2,3][1,2][1,3][1][2,3][2][3][]
                        
šŸ“Š
Each path from root to leaf represents one complete subset. The tree has 2n leaves (one for each subset).

When to Use This Pattern

1

Combinations/Permutations

When you need to generate all possible combinations or arrangements of elements.

2

Include/Exclude Decisions

When each element has a binary choice (take it or leave it).

3

Exploring All Possibilities

When you need to explore all possible states or configurations.

Similar Problems

  • Subsets II (with duplicates)
  • Permutations
  • Combinations
  • Combination Sum
  • Palindrome Partitioning

Step-by-Step Walkthrough

1

Initialize Data Structures

Python
def subsets(self, array_nums):
    output_list = []
    current_subset = []

Create a result list to store all subsets and a temporary list to build the current subset.

2

Define Recursive Function

Python
def explore(position):
    # Base case: reached end of array
    if position >= len(array_nums):
        output_list.append(current_subset.copy())
        return

Check if we've processed all elements. If yes, add a copy of the current subset to results.

3

Include Current Element

Python
    # Choice 1: Include current element
    current_subset.append(array_nums[position])
    explore(position + 1)
    current_subset.pop()  # Backtrack!

Add the element, explore this branch, then remove it (backtrack) to explore the other branch.

4

Exclude Current Element

Python
    # Choice 2: Exclude current element
    explore(position + 1)

Simply move to the next index without adding the current element.

5

Start Recursion and Return

Python
    explore(0)
    return output_list

Kick off the recursion from index 0 and return all collected subsets.

Complete Solution

Python
class Solution:
    def subsets(self, array_nums):
        output_list = []
        current_subset = []

        def explore(position):
            if position >= len(array_nums):
                output_list.append(current_subset.copy())
                return
            
            # Include current element
            current_subset.append(array_nums[position])
            explore(position + 1)
            current_subset.pop()
            
            # Exclude current element
            explore(position + 1)

        explore(0)
        return output_list

Interactive Visualizer

Current Index: -
Current Subset: []
Action: -
Results Count: 0

Enter an array and click Start to visualize

Knowledge Check

Question 1: What is the time complexity of generating all subsets?
A) O(n)
B) O(n²)
C) O(n·2ⁿ)
D) O(2ⁿ)
Explanation: We generate 2ⁿ subsets, and each subset takes O(n) time to copy into the result list.
Question 2: Why must we copy the subset when adding it to results?
A) To improve performance
B) Because backtracking modifies the same list reference
C) To reduce space complexity
D) It's not necessary to copy
Explanation: Without copying, all results would reference the same list, which gets modified during backtracking, resulting in incorrect output.
Question 3: For an array of length 5, how many total subsets exist?
A) 10
B) 16
C) 25
D) 32
Explanation: The formula is 2ⁿ where n is the array length. So 2⁵ = 32 subsets.
Question 4: What does backtracking mean in this context?
A) Going back to the start of the array
B) Undoing a choice to explore alternatives
C) Reversing the array
D) Skipping elements
Explanation: Backtracking means removing an element we just added (popping from subset) so we can explore the branch where that element is excluded.

Code Playground

Try It Yourself

Modify the code below and click "Run" to see the results. Try different approaches!

Output will appear here...

Practice Challenges

1

Modify to Return Only Subsets of Size K

Add a parameter k and only include subsets with exactly k elements.

2

Implement Iterative Approach

Rewrite the solution without recursion using iteration.

3

Add Memoization

Can you optimize with caching? (Hint: this problem doesn't benefit from memoization, but understanding why is valuable!)

Alternative Approaches

Iterative Approach

Build subsets iteratively by adding each element to all existing subsets.

Python
def subsets(self, array_nums):
    output_list = [[]]
    
    for element in array_nums:
        current_size = len(output_list)
        for idx in range(current_size):
            new_subset = output_list[idx] + [element]
            output_list.append(new_subset)
    
    return output_list
šŸ’”
Start with [[]], then for each number, double the subsets by adding the number to each existing subset.

Bit Manipulation Approach

Use binary representation where each bit indicates whether to include an element.

Python
def subsets(self, array_nums):
    array_length = len(array_nums)
    output_list = []
    
    for mask in range(1 << array_length):
        current_subset = []
        for idx in range(array_length):
            if mask & (1 << idx):
                current_subset.append(array_nums[idx])
        output_list.append(current_subset)
    
    return output_list
⚔
For [1,2,3]: mask 0b101 (5 in decimal) means include elements at indices 0 and 2: [1,3]

Approach Comparison

Approach Pros Cons
Backtracking Intuitive, easy to understand decision tree Uses call stack space O(n)
Iterative No recursion, constant extra space Less intuitive logic
Bit Manipulation Elegant, mathematical approach Limited to small arrays (n ≤ 30)

Common Mistakes

āŒ Mistake 1: Not Copying Subset

Wrong
# This will fail!
output_list.append(current_subset)
Correct
# Must copy the list
output_list.append(current_subset.copy())

āŒ Mistake 2: Wrong Base Case

Wrong
if position == len(array_nums) - 1:
Correct
if position >= len(array_nums):

āŒ Mistake 3: Forgetting to Backtrack

Wrong
current_subset.append(array_nums[position])
explore(position + 1)
# Missing pop()!
Correct
current_subset.append(array_nums[position])
explore(position + 1)
current_subset.pop()  # Essential!

āŒ Mistake 4: Starting from Wrong Index

Wrong
# Will cause duplicates
explore(position)
Correct
# Move to next element
explore(position + 1)

Flashcards

Click each card to flip and test your knowledge

What is the base case in the backtracking solution?

When the index reaches or exceeds the array length (position >= len(array_nums)), we've processed all elements and add the current subset to results.

Why is the time complexity O(n·2ⁿ)?

We generate 2ⁿ subsets (each element has 2 choices), and copying each subset into the result takes O(n) time in the worst case.

What does "backtracking" mean?

Undoing a choice (removing an element from the current subset) after exploring one path, so we can explore alternative paths in the decision tree.

Quick Reference

Template Code

Python Template
def backtrack_subsets(array_nums):
    output_list = []
    current_subset = []
    
    def explore(start_pos):
        # Base case
        if start_pos >= len(array_nums):
            output_list.append(current_subset.copy())
            return
        
        # Include current element
        current_subset.append(array_nums[start_pos])
        explore(start_pos + 1)
        current_subset.pop()
        
        # Exclude current element
        explore(start_pos + 1)
    
    explore(0)
    return output_list
šŸ“‹
Remember the pattern: Initialize → Base case → Include & Recurse → Backtrack → Exclude & Recurse
5
Key Steps
2ⁿ
Subsets Generated
O(n)
Recursion Depth

Keep Learning! šŸš€

Now that you've mastered Subsets, try these related problems:

Next Challenges

  • Subsets II (with duplicates)
  • Permutations
  • Combinations
  • Letter Case Permutation

Study Tips

  • Practice drawing decision trees
  • Implement all three approaches
  • Time yourself solving from scratch
  • Review after 1 day, 1 week, 1 month