You are given an array nums of integers, which may contain duplicates. Return all possible subsets.
The solution must not contain duplicate subsets. You may return the solution in any order.
Example 1:
Input: nums = [1,2,1]
Output: [[],[1],[1,2],[1,1],[1,2,1],[2]]Example 2:
Input: nums = [7,7]
Output: [[],[7], [7,7]]Constraints:
1 <= nums.length <= 11-20 <= nums[i] <= 20
You should aim for a solution as good or better than O(n * (2^n)) time and O(n) space, where n is the size of the input array.
A brute-force solution would involve creating a hash set and inserting every subset into it. Then, converting the hash set to a list and returning it. However, this approach would require extra space of O(2^n). Can you think of a better way? Maybe you should sort the input array and observe which recusive calls are resposible to make duplicate subsets.
We can use backtracking to generate subsets of an array. If the input contains duplicates, duplicate subsets may be created. To prevent this, we sort the array beforehand. For example, in [1, 1, 2], sorting allows us to create subsets using the first 1 and skip the second 1, ensuring unique subsets. How can you implement this?
We start by sorting the input array. Then, we recursively iterate through the array from left to right, extending recursive paths by including or excluding each element. To avoid duplicate subsets, we skip an element if it is the same as the previous one. Finally, we return the generated subsets as a list.
This brute-force method generates every possible subset by making a binary choice at each index:
Since duplicates exist, many generated subsets may look identical.
To avoid returning duplicates, we:
In the end, we convert the set of tuples back to a list of lists.
class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = set()
def backtrack(i, subset):
if i == len(nums):
res.add(tuple(subset))
return
subset.append(nums[i])
backtrack(i + 1, subset)
subset.pop()
backtrack(i + 1, subset)
nums.sort()
backtrack(0, [])
return [list(s) for s in res]We want all subsets, but the array may contain duplicates.
If we blindly generate all subsets, we will produce repeated ones.
So we must avoid picking the same value in the same decision level more than once.
Key idea:
i, we make two choices:nums[i] nums[i]But when excluding, if the next number is the same (nums[i] == nums[i+1]), then skipping it now and skipping it later produce the same subset.
So after exploring the "exclude" branch, we skip over all duplicate values to avoid generating duplicate subsets.
We also sort the array first, so duplicates become consecutive and easy to skip.
backtrack(i, subset):i reaches the end → add a copy of subset to the result.nums[i] → recurse on i+1.nums[i]:i forward while the next value is the same (skip duplicates).class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
res = []
nums.sort()
def backtrack(i, subset):
if i == len(nums):
res.append(subset[::])
return
subset.append(nums[i])
backtrack(i + 1, subset)
subset.pop()
while i + 1 < len(nums) and nums[i] == nums[i + 1]:
i += 1
backtrack(i + 1, subset)
backtrack(0, [])
return resWe want to generate all subsets, but duplicates in the input can create repeated subsets.
To avoid duplicates cleanly, instead of making “pick / not pick” decisions, this approach builds subsets by choosing each possible next element—but only once per unique value at each recursion level.
Key idea:
j from the current index to the end.nums[j] is the same as nums[j-1] and j > i, we skip it.backtrack, we push the current subset into res.This ensures:
backtrack(i, subset):j from i to the end:j > i and nums[j] == nums[j-1]: skip duplicate choices.nums[j] into the subset.backtrack(j + 1, subset).backtrack(0, []).class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = []
def backtrack(i, subset):
res.append(subset[::])
for j in range(i, len(nums)):
if j > i and nums[j] == nums[j - 1]:
continue
subset.append(nums[j])
backtrack(j + 1, subset)
subset.pop()
backtrack(0, [])
return resThis iterative method builds subsets step by step.
Normally, for each new number, we add it to every existing subset.
But duplicates cause repeated subsets — so we must avoid recombining duplicates with all previous subsets.
Key idea:
idx: start point for generating new subsets.prev_idx: end point (previous size of result list before adding this number).idx = 0).Example:
For input [1,2,2]
2 extends all subsets. 2 extends only the subsets added when first 2 was processed → no duplicates.nums so duplicates are adjacent.res = [[]].i:nums[i] is the same as nums[i-1] → set idx to the previous end (prev_idx).idx = 0.prev_idx = len(res) (the boundary of old subsets).res[j] where j ranges from idx to prev_idx - 1:nums[i].res.res.class Solution:
def subsetsWithDup(self, nums: List[int]) -> List[List[int]]:
nums.sort()
res = [[]]
prev_Idx = idx = 0
for i in range(len(nums)):
idx = prev_idx if i >= 1 and nums[i] == nums[i - 1] else 0
prev_idx = len(res)
for j in range(idx, prev_idx):
tmp = res[j].copy()
tmp.append(nums[i])
res.append(tmp)
return res