74. Subsets II
View on NeetCode
View on LeetCode
Problem
Given an integer array nums that may contain duplicates, return all possible subsets (the power set).
The solution set must not contain duplicate subsets. Return the solution in any order.
Example 1:
Input: nums = [1,2,2]
Output: [[],[1],[1,2],[1,2,2],[2],[2,2]]
Example 2:
Input: nums = [0]
Output: [[],[0]]
Constraints:
1 <= nums.length <= 10-10 <= nums[i] <= 10
Solution
Approach: Backtracking with Duplicate Skipping
This builds on the Subsets backtracking pattern. The for loop picks which value to place at the current position, and the recursion moves to the next position. The for loop is horizontal (sibling choices), and the recursion is vertical (going deeper).
[]
/ | \
[1] [2] [2] ← skip! same value at same position
/ \ |
[1,2] [1,2] [2,2]
| ↑ skip!
[1,2,2]
With duplicates in the input, two siblings can have the same value, producing identical subtrees. Sorting the array groups duplicates together, then the skip condition i > start and nums[i] == nums[i-1] catches them: “if a sibling before me already used this same value, skip me.”
Implementation
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
def backtrack(start, path):
result.append(path[:])
for i in range(start, len(nums)):
if i > start and nums[i] == nums[i - 1]:
continue
path.append(nums[i])
backtrack(i + 1, path)
path.pop()
backtrack(0, [])
return result
Complexity Analysis
- Time Complexity: O(n * 2^n), where n is the length of nums. Sorting is O(n log n), backtracking generates up to 2^n subsets, each taking O(n) to copy.
- Space Complexity: O(n) for recursion depth (not counting output).
Key Insights
-
For Loop = Siblings, Recursion = Depth: The for loop iterates over candidates for the current position. Each candidate spawns a recursive call that fills the next position. The
startparameter prevents going backwards through the sorted array. -
Skip Condition:
i > startmeans “not the first choice at this level.” Combined withnums[i] == nums[i-1], it skips sibling branches that would produce identical subtrees. Sorting is required so duplicates are adjacent. -
First Occurrence Always Allowed: At any level, the first duplicate is picked (it might lead to subsets like
[1,2,2]). Only the second duplicate at the same level is skipped, because its entire subtree is identical to the first.