271. Subsets II
π LeetCode Problem: 90. Subsets II
π Difficulty: Medium
π·οΈ Topics: Array, Backtracking, Bit Manipulation
Problem Statement
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
π ELI5: The Simple Idea!
Think of Subsets I, but with duplicate toppings:
Subsets I (No duplicates):
Toppings: [Cheese, Pepperoni, Mushroom]
All different β All combinations valid
Result: 8 subsets (2Β³)
Subsets II (With duplicates):
Toppings: [Cheese, Pepperoni, Pepperoni]
β β
Same topping!
Problem: [Cheese, Pepperoniβ] same as [Cheese, Pepperoniβ]
Solution: When deciding to SKIP or TAKE, if current element
equals previous element AND we're at same decision level,
SKIP it to avoid duplicates!
The Key Insight:
nums = [1, 2, 2]
β β β
2β 2β (two 2's)
WITHOUT handling duplicates:
[1, 2β] and [1, 2β] β Same subset, appears twice! β
WITH handling duplicates:
At each level, if element equals previous element,
and we're not taking it in a sequence,
SKIP to avoid generating duplicate subsets! β
π¨ Visual Understanding
The Problem with Duplicates
nums = [1, 2, 2] (unsorted)
Decision tree WITHOUT handling duplicates:
index=0
Consider 1
/ \
SKIP 1 TAKE 1
curr=[] curr=[1]
| |
index=1 index=1
Consider 2β Consider 2β
/ \ / \
SKIP 2β TAKE 2β SKIP 2β TAKE 2β
curr=[] curr=[2β] curr=[1] curr=[1,2β]
| | | |
index=2 index=2 index=2 index=2
Consider Consider Consider Consider
2β 2β 2β 2β
/ \ / \ / \ / \
[] [2β] [2β][2β,2β] [1][1,2β] [1,2β][1,2β,2β]
Result: [[],[2β],[2β],[2β,2β],[1],[1,2β],[1,2β],[1,2β,2β]]
β β β
[2] appears in different forms!
[1,2] appears in different forms!
DUPLICATES! β
The Solution: Sort + Skip Duplicates at Same Level
Step 1: SORT the array
nums = [1, 2, 2] β Already sorted
Sorting groups duplicates together:
[1, 2β, 2β] (indices 0, 1, 2)
Step 2: Skip duplicates at SAME RECURSION LEVEL
Key Rule:
When making SKIP/TAKE decision at index i:
If nums[i] == nums[i-1] AND we SKIPPED nums[i-1]
β SKIP nums[i] too!
Why? Because:
- We already explored all subsets without nums[i-1]
- nums[i] == nums[i-1]
β All subsets without nums[i] are same as without nums[i-1]
β No need to explore again!
Decision Tree WITH Duplicate Handling
nums = [1, 2, 2] (sorted)
index=0
Consider 1
/ \
SKIP 1 TAKE 1
curr=[] curr=[1]
| |
index=1 index=1
Consider 2β Consider 2β
/ \ / \
SKIP 2β TAKE 2β SKIP 2β TAKE 2β
curr=[] curr=[2β] curr=[1] curr=[1,2β]
| | | |
index=2 index=2 index=2 index=2
Consider Consider Consider Consider
2β 2β 2β 2β
/ \ / \ / \ / \
At index=2 after SKIP 2β:
2β == 2β AND we just SKIPPED 2β
β SKIP 2β too! (Don't explore)
Final result: [[],[2β],[2β,2β],[1],[1,2β],[1,2β,2β]]
β β β β
[2] once [2,2] once [1,2] once [1,2,2] once
NO DUPLICATES! β
π¨ How Tracking Works - Visual Step by Step
Complete State Tracking for nums=[1,2,2]
Initial: nums = [1, 2, 2] (sorted)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
LEVEL 0: index=0, Consider element 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
State:
nums = [1, 2, 2]
index = 0
curr = []
prevSkipped = false (not applicable at index 0)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
BRANCH A: SKIP 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call: backtrack(nums, 1, [], result, skipped=true)
State:
index = 1
curr = []
prevSkipped = true (we skipped 1)
βββββββββββββββββββββββββββββββββββββββββββ
β Path: Skipped 1 β
β Current subset: [] β
β Next: Consider 2 at index 1 β
βββββββββββββββββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUB-BRANCH A1: SKIP 2 at index 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call: backtrack(nums, 2, [], result, skipped=true)
State:
index = 2
curr = []
prevSkipped = true (we skipped 2 at index 1)
βββββββββββββββββββββββββββββββββββββββββββ
β Path: Skipped 1, Skipped 2 β
β Current subset: [] β
β Next: Consider 2 at index 2 β
βββββββββββββββββββββββββββββββββββββββββββ
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
Check: Should we explore SKIP 2 at index 2?
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
Decision Logic:
nums[2] = 2
nums[1] = 2
nums[2] == nums[1]? YES β
Did we SKIP at index 1? YES β
β SKIP this duplicate! Don't explore!
Visualization:
Index 1: [2] β SKIPPED
Index 2: [2] β SKIP TOO (duplicate at same level)
Why? If we skip first 2, skipping second 2 gives
same result (empty set). Already explored!
Skip SKIP branch at index 2 β
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
TAKE 2 at index 2 (After skipping 2 at index 1)
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
But wait! We can TAKE 2 at index 2 even though we SKIPPED
at index 1, because TAKE creates different subset!
Call: backtrack(nums, 3, [2], result, skipped=false)
State:
index = 3 (end!)
curr = [2]
BASE CASE: Add [2] β
Result: [[],[2]]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUB-BRANCH A2: TAKE 2 at index 1
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Back to index=1, curr=[]
TAKE 2:
Step 2: curr.add(nums[1]) β curr = [2]
Call: backtrack(nums, 2, [2], result, skipped=false)
State:
index = 2
curr = [2]
prevSkipped = false (we TOOK at index 1)
βββββββββββββββββββββββββββββββββββββββββββ
β Path: Skipped 1, Took 2 at index 1 β
β Current subset: [2] β
β Next: Consider 2 at index 2 β
βββββββββββββββββββββββββββββββββββββββββββ
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
SKIP 2 at index 2 (After taking 2 at index 1)
Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β· Β·
Decision Logic:
nums[2] = 2
nums[1] = 2
nums[2] == nums[1]? YES β
Did we SKIP at index 1? NO β (we TOOK it!)
β ALLOW this exploration! Different scenario!
Call: backtrack(nums, 3, [2], result, skipped=true)
BASE CASE: Add [2] β (Wait, already added?)
Actually, this adds [2] again from different path.
We'll handle this by understanding the logic better...
Let me re-explain with the CORRECT approach:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CORRECTED UNDERSTANDING - The Skip Rule
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The actual rule is simpler:
At index i, when making SKIP decision:
if (i > start && nums[i] == nums[i-1] && !tookPrevious) {
Skip this SKIP branch!
}
Better way to think:
We don't track "skipped" flag.
Instead, we notice:
- In SKIP branch: don't increment index, so when we come back
to TAKE, we haven't "moved on" from previous element
Actual implementation:
Just ensure when at index i in SKIP path:
If nums[i] == nums[i-1], don't explore
(because SKIP of i-1 already covered this)
Let me show you the cleaner understanding:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
CLEANER APPROACH - The Standard Pattern
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
The standard approach uses a FOR LOOP, not separate SKIP/TAKE calls!
void backtrack(nums, start, curr, result):
Add curr to result
for i = start to nums.length-1:
// Skip duplicates at same level
if (i > start && nums[i] == nums[i-1]):
continue // Skip this iteration
curr.add(nums[i])
backtrack(nums, i+1, curr, result)
curr.remove(last)
This is easier! Let me trace this:
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
TRACE WITH FOR LOOP APPROACH: nums=[1,2,2]
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 1: backtrack([1,2,2], start=0, curr=[], result)
Add [] to result β
Result: [[]]
Loop i=0: nums[0]=1
Not duplicate (i==start)
curr = [1]
β backtrack([1,2,2], 1, [1], result)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 2: backtrack([1,2,2], start=1, curr=[1], result)
Add [1] to result β
Result: [[],[1]]
Loop i=1: nums[1]=2
Not duplicate (i==start)
curr = [1,2]
β backtrack([1,2,2], 2, [1,2], result)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 3: backtrack([1,2,2], start=2, curr=[1,2], result)
Add [1,2] to result β
Result: [[],[1],[1,2]]
Loop i=2: nums[2]=2
Not duplicate (i==start)
curr = [1,2,2]
β backtrack([1,2,2], 3, [1,2,2], result)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 4: backtrack([1,2,2], start=3, curr=[1,2,2], result)
Add [1,2,2] to result β
Result: [[],[1],[1,2],[1,2,2]]
Loop: start=3 >= length=3, no iterations
Return
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[Back to Call 3]
Backtrack: curr = [1,2]
Loop finished
Return
[Back to Call 2]
Backtrack: curr = [1]
Loop i=2: nums[2]=2
Check: i > start? YES (2 > 1)
Check: nums[2] == nums[1]? YES (2 == 2)
β SKIP THIS! (Continue)
βββββββββββββββββββββββββββββββββββββββββββ
β CRITICAL: Skipped duplicate! β
β i=2, start=1, nums[2]==nums[1] β
β Already explored [1,2,...] from i=1 β
β No need to explore [1,2,...] from i=2 β
βββββββββββββββββββββββββββββββββββββββββββ
Loop finished
Return
[Back to Call 1]
Backtrack: curr = []
Loop i=1: nums[1]=2
Not duplicate (i > start but diff from previous? No, i=1, start=0)
curr = [2]
β backtrack([1,2,2], 2, [2], result)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 5: backtrack([1,2,2], start=2, curr=[2], result)
Add [2] to result β
Result: [[],[1],[1,2],[1,2,2],[2]]
Loop i=2: nums[2]=2
Not duplicate (i==start)
curr = [2,2]
β backtrack([1,2,2], 3, [2,2], result)
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Call 6: backtrack([1,2,2], start=3, curr=[2,2], result)
Add [2,2] to result β
Result: [[],[1],[1,2],[1,2,2],[2],[2,2]]
Return
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
[Back to Call 5]
Backtrack: curr = [2]
Return
[Back to Call 1]
Backtrack: curr = []
Loop i=2: nums[2]=2
Check: i > start? YES (2 > 0)
Check: nums[2] == nums[1]? YES (2 == 2)
β SKIP THIS! (Continue)
βββββββββββββββββββββββββββββββββββββββββββ
β CRITICAL: Skipped duplicate! β
β Already explored [2,...] from i=1 β
β No need to explore [2,...] from i=2 β
βββββββββββββββββββββββββββββββββββββββββββ
Loop finished
Return
βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Final Result: [[],[1],[1,2],[1,2,2],[2],[2,2]]
All unique! No duplicates! β
The Key Visualization - When Duplicate is Skipped
At Call 2: start=1, trying elements from index 1 onwards
i=1: nums[1]=2
Check: i > start? NO (1 == 1)
β Process this β
β Generates: [1,2] and [1,2,2]
i=2: nums[2]=2
Check: i > start? YES (2 > 1) β
Check: nums[2] == nums[1]? YES (2 == 2) β
β SKIP! β
Why skip?
Already processed nums[1]=2
nums[2] is duplicate of nums[1]
At SAME LEVEL (both children of [1])
Would generate same subsets!
Visual:
[1] β [1,2] β [1,2,2] β (from i=1)
[1] β [1,2] β [1,2,2] β (from i=2, duplicate!)
π― Approach 1: Backtracking with For Loop ββ
The Standard Solution (Easier than Skip/Take!)
Algorithm:
1. SORT the array (crucial!)
2. Use backtracking with FOR LOOP:
- Add current subset
- For each element from start:
- Skip duplicates at same level: if (i > start && nums[i] == nums[i-1])
- Take element, recurse, backtrack
Implementation
import java.util.*;
/**
* Backtracking with duplicate handling
* Time: O(n Γ 2^n) - Same as Subsets I
* Space: O(n) - Recursion depth
*/
class Solution {
public List<List<Integer>> subsetsWithDup(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums); // CRITICAL: Sort first!
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(int[] nums, int start,
List<Integer> curr, List<List<Integer>> result) {
// Add current subset
result.add(new ArrayList<>(curr));
// Try adding each element from start onwards
for (int i = start; i < nums.length; i++) {
// Skip duplicates at same recursion level
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// Take element
curr.add(nums[i]);
// Recurse
backtrack(nums, i + 1, curr, result);
// Backtrack
curr.remove(curr.size() - 1);
}
}
public static void main(String[] args) {
Solution sol = new Solution();
System.out.println(sol.subsetsWithDup(new int[]{1,2,2}));
System.out.println(sol.subsetsWithDup(new int[]{0}));
}
}
β° Time: O(n Γ 2^n) - Generate 2^n subsets, each takes O(n) to copy
πΎ Space: O(n) - Maximum recursion depth
β οΈ Common Mistakes
Mistake 1: Not sorting the array
// β WRONG - Can't identify duplicates without sorting!
public List<List<Integer>> subsetsWithDup(int[] nums) {
// No sorting!
backtrack(nums, 0, new ArrayList<>(), result);
}
// nums = [2,1,2] β Can't tell 2's are together!
// β CORRECT - Sort first
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums); // [1,2,2] now!
backtrack(nums, 0, new ArrayList<>(), result);
}
Mistake 2: Wrong duplicate check condition
// β WRONG - Checks with wrong index
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// Would skip [1,2,2] because it blocks 2nd 2 even in path!
// β CORRECT - Check against start
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// Only skips at same recursion level, not in path!
Mistake 3: Adding subset at base case instead of every level
// β WRONG - Only adds complete sets
if (start == nums.length) {
result.add(new ArrayList<>(curr));
return;
}
// β CORRECT - Add at every level
result.add(new ArrayList<>(curr));
// Then continue exploring
Mistake 4: Not backtracking properly
// β WRONG - curr keeps growing!
for (int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i-1]) continue;
curr.add(nums[i]);
backtrack(nums, i + 1, curr, result);
// Forgot to remove!
}
// β CORRECT - Remove after recursion
for (int i = start; i < nums.length; i++) {
if (i > start && nums[i] == nums[i-1]) continue;
curr.add(nums[i]);
backtrack(nums, i + 1, curr, result);
curr.remove(curr.size() - 1); // Backtrack!
}
Mistake 5: Using i > 0 instead of i > start
// β WRONG - Blocks duplicates even in same path!
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// For [1,2,2], would generate [1,2] but not [1,2,2]!
// β CORRECT - Only skip at same level
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// Allows [1,2,2] in path, skips duplicate at same level
π― Pattern Recognition
Problem Type: Subsets with Duplicates
Core Pattern: Backtracking + Sort + Skip Duplicates at Same Level
When to Apply:
β Generate all subsets with duplicates
β Input may have duplicate values
β Must avoid duplicate subsets in result
β Power set with duplicates
Recognition Keywords:
- "may contain duplicates"
- "must not contain duplicate subsets"
- "all possible subsets"
- "power set"
Comparison with Related Problems:
ββββββββββββββββββββ¬βββββββββββββ¬βββββββββββββββ¬βββββββββββββββ
β Problem β Duplicates β Skip Logic β Reuse β
ββββββββββββββββββββΌβββββββββββββΌβββββββββββββββΌβββββββββββββββ€
β Subsets β No β Not needed β No β
β Subsets II β Yes β MUST skip! β No β
β Combination Sum β No β Not needed β Yes (pass i) β
β Combination II β Yes β MUST skip! β No (pass i+1)β
ββββββββββββββββββββ΄βββββββββββββ΄βββββββββββββββ΄βββββββββββββββ
Key Components:
ββββββββββββββββββββββββββββββββββββββββββββββ
β SORT: Group duplicates together β
β Skip: if (i > start && arr[i]==arr[i-1]) β
β Add at every level: Not just base case β
β For loop: Try elements from start β
β Time: O(n Γ 2^n), Space: O(n) β
ββββββββββββββββββββββββββββββββββββββββββββββ
π§ Interview Strategy
Step 1: "This is Subsets II - has duplicates unlike Subsets I"
Step 2: "Key: SORT first, then skip duplicates at same level"
Step 3: "Use for loop with skip condition: i > start && nums[i] == nums[i-1]"
Key Points to Mention:
- Must sort array first to group duplicates
- Skip duplicates only at same recursion level
- Condition: i > start (not i > 0!)
- Add subset at every level (not just base case)
- Same time complexity as Subsets I
Walk Through Example:
"For nums=[1,2,2]:
Sort: [1,2,2] β
Start with []
Try i=0 (1): Add 1
From [1], try i=1 (2): Add 2 β [1,2]
From [1,2], try i=2 (2): Add 2 β [1,2,2] β
Back to [1,2], try i=2 (2): SKIP! (i>start && 2==2)
Back to [1], try i=2 (2): SKIP! (i>start && 2==2)
Try i=1 (2): Add 2
From [2], try i=2 (2): Add 2 β [2,2] β
Try i=2 (2): SKIP! (i>start && 2==2)
Result: [[],[1],[1,2],[1,2,2],[2],[2,2]]"
Why i > start:
"i > start means we're not at first element of this level.
If nums[i] == nums[i-1] and we're not first,
then we already explored this value at this level.
Example: At level [1], we try i=1 (2) first.
Then we come to i=2 (2) - but it's duplicate!
i > start (2 > 1) and nums[2] == nums[1]
β Skip to avoid duplicate subsets!"
Complexity:
"Time: O(n Γ 2^n) - Same as Subsets I
Space: O(n) for recursion depth
Sorting adds O(n log n) but doesn't affect overall"
π Quick Revision Notes
π― Core Concept:
Subsets II: Like Subsets I but array has duplicates. MUST SORT first to group duplicates. Skip duplicates at same recursion level: if (i > start && nums[i] == nums[i-1]) continue. This prevents generating duplicate subsets like [1,2] twice from two different 2's. Time: O(n Γ 2^n).
β‘ Quick Implementation:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
public class Solution {
public List<List<Integer>> subsetsWithDup(int[] a) {
return recursive(a);
}
private List<List<Integer>> recursive(int[] a) {
// Approach:
// Almost same as subsets-i problem
// Main difference is that if we do like and if duplicate
// elements, we can never reach next element.
// In other words, that approach is not possible for duplicates scenario
// So, use for loop
List<List<Integer>> res = new ArrayList<>();
// step 1: sort the array
Arrays.sort(a);
recursive(a, 0, new ArrayList<>(), res);
return res;
}
private void recursive(int[] a, int start, ArrayList<Integer> curr, List<List<Integer>> res) {
// step 7: add every curr to result including empty list
res.add(new ArrayList<>(curr));
// step 2: start at each element starting from index 'start'
for (int i = start; i < a.length; i++) {
// step 3: skipping duplicates
if (i > start && a[i] == a[i - 1]) {
continue;
}
// step 4: add to curr
curr.add(a[i]);
// step 5: proceed to next index
recursive(a, i + 1, curr, res);
// step 6: remove the added element
curr.remove(curr.size() - 1);
}
}
public static void main(String[] args) {
Solution s = new Solution();
// [[],[1],[1,2],[1,2,2],[2],[2,2]]
System.out.println(s.subsetsWithDup(new int[] { 1, 2, 2 }));
// [[],[0]]
System.out.println(s.subsetsWithDup(new int[] { 0 }));
}
}
π Key Insights:
- Pattern: Subsets I + Sort + Skip Duplicates
- MUST SORT: Groups duplicates together
- Skip Condition:
i > start && nums[i] == nums[i-1] - Why i > start: Only skip at same level, not in path
- Allows in path: [1,2,2] valid (both 2's in same path)
- Blocks at level: [1,2] from 2β and [1,2] from 2β (only first)
- Time: O(n Γ 2^n), Space: O(n) β
πͺ Memory Aid:
"Sort first, skip same level, allow same path!"
"i > start is the key, not i > 0!" β¨
β οΈ Critical Difference - i > start vs i > 0:
Using i > 0 (WRONG):
nums = [1,2,2], sorted
Path: [1]
At level trying to add from index 1:
i=1: nums[1]=2, add it β [1,2]
Now at level trying to add from index 2:
i=2: nums[2]=2
Check: i > 0? YES
Check: nums[2] == nums[1]? YES
β SKIP!
Result: [1,2] but NOT [1,2,2]! β
Blocked 2nd 2 even though it's in SAME PATH!
Using i > start (CORRECT):
nums = [1,2,2], sorted
Path: [1]
At level trying to add from index 1:
i=1: nums[1]=2, add it β [1,2]
Now at level trying to add from index 2:
start=2 (from recursion)
i=2: nums[2]=2
Check: i > start? NO (2 == 2)
β PROCESS! Add 2 β [1,2,2] β
At SAME level from [1]:
i=1: nums[1]=2, explored
i=2: nums[2]=2
Check: i > start? YES (2 > 1)
Check: nums[2] == nums[1]? YES
β SKIP! (Already explored from i=1)
Result: Both [1,2] and [1,2,2] β
π§ͺ Edge Cases
Case 1: All duplicates
Input: nums = [1,1,1]
Output: [[],[1],[1,1],[1,1,1]]
Each level adds one more 1
Case 2: No duplicates
Input: nums = [1,2,3]
Output: [[],[1],[1,2],[1,2,3],[2],[2,3],[3]]
Same as Subsets I
Case 3: Multiple duplicate sets
Input: nums = [4,4,4,1,4]
After sort: [1,4,4,4,4]
Output: [[],[1],[1,4],[1,4,4],[1,4,4,4],[1,4,4,4,4],
[4],[4,4],[4,4,4],[4,4,4,4]]
Case 4: Two pairs of duplicates
Input: nums = [1,2,2,3,3]
Output: [[],[1],[1,2],[1,2,2],[1,2,2,3],[1,2,2,3,3],
[1,2,3],[1,2,3,3],[1,3],[1,3,3],
[2],[2,2],[2,2,3],[2,2,3,3],[2,3],[2,3,3],
[3],[3,3]]
All handled correctly! β
π Complexity Analysis
Time Complexity: O(n Γ 2^n)
Same as Subsets I!
Why?
Even with duplicates, we generate at most 2^n subsets
(Actually fewer due to duplicates, but big-O is same)
Each subset: O(n) to copy
Total: n Γ 2^n
Sorting: O(n log n)
But 2^n dominates for n > 4
Example:
n=3: 2Β³ = 8 max subsets
n=4: 2β΄ = 16 max subsets
n=10: 2ΒΉβ° = 1024 max subsets
Space Complexity: O(n)
Recursion stack: O(n)
Maximum depth = n
Current list: O(n)
Total: O(n) auxiliary space
Output space: O(n Γ 2^n) - not counted
π Related Problems
Same Core Pattern: - Subsets (LC 78) - Without duplicates (easier) - Combination Sum II (LC 40) - With duplicates + target - Permutations II (LC 47) - Permutations with duplicates
Similar Skip Logic: - Combination Sum II (LC 40) - Same skip pattern! - Permutations II (LC 47) - Same skip pattern! - 3Sum (LC 15) - Skip duplicates in two pointers
Key Learning:
The i > start && arr[i] == arr[i-1] pattern appears in MANY problems with duplicates!
Happy practicing! π―
Note: This problem teaches the essential "skip duplicates at same level" technique! The condition i > start && nums[i] == nums[i-1] is crucial for many problems: Combination Sum II, Permutations II, and more. Master the difference between "same level" (skip) and "same path" (allow) and you'll solve all duplicate problems easily! πͺβ¨