LeetCode #90 Medium

Subsets II

Given an integer array nums that may contain duplicates, return all possible subsets (the power set) without duplicate subsets.

backtrackingrecursionbit manipulation
Open on LeetCode ↗
02

Intuition

Subsets is a take/skip walk over the decision tree. With duplicates, sort first so equal values sit together, then within one recursion level skip a value identical to the one just tried — that prevents building the same subset twice.

How to spot this pattern

Subsets with duplicates. Sorting makes equal values adjacent, and the guard i > start skips a repeat only when it would start a sibling branch — the same value is still allowed deeper in the path. That distinction between "same depth" and "same path" is the crux of every duplicate-handling backtracking problem.

03

Approach

1

Start from the plain Subsets walk

Without duplicates, every node on a backtracking path is a valid subset: append a copy of the current path, then for each later index choose it, recurse, and pop. That generates all 2ⁿ subsets. The problem is that duplicates in the input would make some of those subsets identical.

2

Sort so duplicates are adjacent

Sort nums first. Now equal values are neighbors, which lets you detect a repeat with a simple nums[i] == nums[i-1] check during the loop.

3

Skip duplicates within a level

Inside the for-loop, if i > start and nums[i] == nums[i-1], continue — skip it. This says: at this branching point we've already explored a subtree starting with this value, so starting another with the same value would duplicate subsets. The i > start guard still allows the value to be used (just not re-started at the same level), so [1,2,2] style subsets are kept exactly once.

04

Solution & live demo

1class Solution:
2 def subsetsWithDup(self, nums):
3 nums.sort()
4 res = []
5 def backtrack(start, path):
6 res.append(path[:])
7 for i in range(start, len(nums)):
8 if i > start and nums[i] == nums[i - 1]:
9 continue
10 path.append(nums[i])
11 backtrack(i + 1, path)
12 path.pop()
13 backtrack(0, [])
14 return res
05

Common pitfalls

Skipping with i > 0 instead of i > start

✗ Wrong
if i > 0 and nums[i] == nums[i - 1]:
    continue
✓ Right
if i > start and nums[i] == nums[i - 1]:
    continue

i > 0 also blocks the first candidate of a branch, so [2, 2] can never be built from two equal values. The duplicate must be skipped only when it repeats a choice already tried at this same level.

Forgetting to sort

✗ Wrong
def subsetsWithDup(self, nums):
    res = []
✓ Right
nums.sort()

The skip test compares against the immediately preceding element, which only identifies duplicates when equal values are adjacent. On unsorted input duplicates scatter and slip through.

Deduplicating the results afterwards

✗ Wrong
return [list(x) for x in {tuple(sorted(s)) for s in res}]
✓ Right
if i > start and nums[i] == nums[i - 1]: continue

It produces the right answer but generates every duplicate subset first, then pays to hash and discard them. Pruning at the branch stops the work before it happens.

06

Edge cases

All duplicates, e.g. [2,2,2]

Yields [[], [2], [2,2], [2,2,2]] — each multiplicity once, no repeats.

No duplicates

The skip never triggers; behaves like ordinary Subsets.

Empty input

Returns [[]] — just the empty subset.

07

Complexity

Time
O(n·2ⁿ)
Space
O(n)
Up to 2ⁿ subsets, each O(n) to copy; recursion depth n. Sorting is O(n log n).