14.11 Subsets II
14.11.1 Problem Metadata
- Platform: LeetCode
- Problem ID: 90
- Difficulty: Medium
- URL: https://leetcode.com/problems/lc-subsets-ii/
- Tags: NeetCode 150
- Techniques: Backtracking: Choose-Explore-Unchoose, Array, Backtracking
14.11.2 Description
Given an integer array that may contain duplicates, return all possible subsets (the power set) without duplicates.
14.11.5 Solution - Backtracking with Skip Logic
14.11.5.1 Walkthrough
This problem uses the 2-dimensional traversal mental model of backtracking (see Pattern 1: Choose-Explore-Unchoose):
- Outer dimension (recursion parameter):
start/ depth in the decision tree - controls which subset of elements we’re considering - Inner dimension (for-loop): Iterates through available indices
ifromstarttonums.length - 1at each depth
The backtracking explores a conceptual grid where each row represents a depth level and each column represents a choice of including or excluding an element. At each node, we append the current path (representing one subset) to results.
Key mechanism: Sort nums so duplicates are adjacent. During recursion, skip elements equal to the previous when at the same depth (i > start && nums[i] == nums[i-1]) to avoid generating duplicate subsets.
14.11.5.3 Implementation Steps
- Sort
numsto group duplicates together. - Initialize empty
resultlist to store all subsets. - Define
backtrack(nums, start, path, result):- Add a copy of
pathtoresult(every path represents a valid subset). - For each index
ifromstarttonums.length - 1:- Skip if
i > startandnums[i] == nums[i - 1](avoid duplicate subsets) - Add
nums[i]topath(choose) - Recursively call
backtrack(nums, i + 1, path, result)(explore) - Remove last element from
path(unchoose)
- Skip if
- Add a copy of
- Call
backtrack(nums, 0, new ArrayList<>(), result)to start, then returnresult.
14.11.5.4 Code - Java
public List<List<Integer>> subsetsWithDup(int[] nums) {
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
backtrack(nums, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(int[] nums, int start, List<Integer> path,
List<List<Integer>> result) {
// Add current subset to results (every path is a valid subset)
result.add(new ArrayList<>(path));
// Try adding each remaining element
for (int i = start; i < nums.length; i++) {
// Skip duplicates: only use first occurrence at each recursion level
if (i > start && nums[i] == nums[i - 1]) {
continue;
}
// Choose: add current element
path.add(nums[i]);
// Explore: recurse with next start index
backtrack(nums, i + 1, path, result);
// Unchoose: remove element for next iteration
path.remove(path.size() - 1);
}
}