Leetcode 90 - Subsets II
Understanding the Problem
The goal is to return all possible subsets (the power set) of an integer array nums that may contain duplicates. The solution set must not contain duplicate subsets, and the subsets can be returned in any order.
To solve this efficiently using backtracking, we model the process as a decision tree where at each step, we decide whether to include an element or skip it. However, because the input array contains duplicate values, picking identical values at the same decision level would generate identical branches, leading to duplicate subsets.
- Sorting for Duplicate Grouping: By sorting
numsinitially, we ensure all duplicate elements are adjacent. This allows us to easily detect and skip identical choices. - State Tracking: We use an index
idxto track our current position in the array and a listcurr_listrepresenting the subset built along the current path. - Pruning Strategy: When iterating through the available elements starting from
idx, we allow the first occurrence of a number (i == idx). For subsequent iterations at the same depth level (i > idx), ifnums[i] == nums[i-1], we skip it. This ensures we don't start identical recursion branches with the same value.
Solution Implementation
Code
from typing import List
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
result = []
# Sort the array so that duplicate elements are adjacent
nums.sort()
def dfs(idx: int, curr_list: List[int]):
# Every state reached in the state-space tree is a valid subset
result.append(curr_list.copy())
for i in range(idx, len(nums)):
# Skip duplicate elements at the current recursion level
if i > idx and nums[i] == nums[i-1]:
continue
# Make the choice
curr_list.append(nums[i])
# Explore deeper choices (move to the next index)
dfs(i + 1, curr_list)
# Undo the choice (backtrack)
curr_list.pop()
dfs(0, [])
return result
Time Complexity
For an array of size \(n\), a power set can have at most \(2^n\) subsets. In the worst-case scenario where all elements are distinct, the algorithm explores all combinations. At each step, copying the curr_list into the result array takes \(O(n)\) time. Sorting the initial array takes \(O(n \log n)\) time.
Overall time complexity — \(O\left(n \cdot 2^n\right)\)
Space Complexity
- Recursion Stack: The maximum depth of the recursion tree is equal to the number of elements in
nums, giving a call stack depth of \(O(n)\). - State Tracking: The
curr_liststores at most \(n\) elements at any point during execution, taking \(O(n)\) auxiliary space.
Overall space complexity — \(O(n)\) (excluding the space required to store the final output)
Additional Resources
- https://neetcode.io/problems/subsets-ii
- https://leetcode.com/problems/subsets-ii/