-
Notifications
You must be signed in to change notification settings - Fork 273
Subsets II
TIP103 Unit 8 Session 2 (Click for link to problem statements)
- 💡 Difficulty: Medium
- ⏰ Time to complete: 25-30 mins
- 🛠️ Topics: Backtracking, Recursion, Arrays, Sorting
Understand what the interviewer is asking for by using test cases and questions about the problem.
- Established a set (2-3) of test cases to verify their own solution later.
- Established a set (1-2) of edge cases to verify their solution handles complexities.
- Have fully understood the problem and have no clarifying questions.
- Have you verified any Time/Space Constraints for this problem?
-
Q: What is a subset (and what is the power set)?
- A: A subset is any selection of elements from
nums, including the empty selection and the full list. The power set is the collection of all such subsets.
- A: A subset is any selection of elements from
-
Q: The input may contain duplicate values. How does that affect the output?
- A: Duplicate values can produce identical subsets through different choices (picking the first
2vs. the second2in[1, 2, 2]). The solution set must not contain duplicate subsets, so we need a way to generate each distinct subset exactly once.
- A: Duplicate values can produce identical subsets through different choices (picking the first
-
Q: Does the order of subsets in the output matter?
- A: No, the subsets can be returned in any order, as long as every distinct subset appears exactly once.
HAPPY CASE
Input: nums = [1, 2, 2]
Output: [[], [1], [1, 2], [1, 2, 2], [2], [2, 2]]
Explanation: [1, 2] appears only once even though it could be formed with either of the two 2s. All 6 distinct subsets are returned.
EDGE CASE
Input: nums = []
Output: [[]]
Explanation: The power set of an empty list contains exactly one subset: the empty subset.
Input: nums = [2, 2, 2]
Output: [[], [2], [2, 2], [2, 2, 2]]
Explanation: Even though there are 8 ways to choose elements, only 4 distinct subsets exist when every element is the same.
Match what this problem looks like to known categories of problems, e.g. Linked List or Dynamic Programming, and strategies or patterns in those categories.
For Generating Combinations/Subsets, we can consider the following approaches:
- Backtracking: Build subsets incrementally by choosing or skipping each element, recording every partial selection as a valid subset. This is the standard pattern for power-set problems.
- Sorting + Skip Duplicates: Sort the input first so equal values sit next to each other, then skip a value when it repeats its neighbor at the same decision level. This deduplicates the output without needing a set of seen subsets.
Plan the solution with appropriate visualizations and pseudocode.
General Idea:
Sort nums so duplicates are adjacent. Then run a backtracking DFS: at each call, record the current partial selection as a subset, then try extending it with each remaining element. To avoid duplicate subsets, when two equal values are both available at the same level of the decision tree, only branch on the first one — the later copies would rebuild subsets we have already produced.
1) Sort nums so equal values are adjacent.
2) Initialize an empty results list.
3) Define a backtracking helper backtrack(start, current):
a) Append a copy of current to the results (every node in the tree is a valid subset).
b) For each index i from start to the end of nums:
i) If i > start and nums[i] == nums[i - 1], skip it (duplicate at this level).
ii) Append nums[i] to current (choose).
iii) Recurse with backtrack(i + 1, current) (explore).
iv) Pop nums[i] off current (un-choose).
4) Call backtrack(0, []) and return the results list.
- Forgetting to sort first — the duplicate-skip check
nums[i] == nums[i - 1]only works when equal values are adjacent. - Writing the skip condition as
i > 0instead ofi > start, which wrongly blocks legitimate runs of duplicates like[2, 2]. - Appending
currentitself instead of a copy (current[:]), so every recorded subset mutates as the recursion continues. - Forgetting to pop after the recursive call, leaving stale elements in
current.
Implement the code to solve the algorithm.
def subsets_with_dup(nums):
nums = sorted(nums) # Sort so duplicates sit next to each other
subsets = []
def backtrack(start, current):
# Every node in the decision tree is a valid subset
subsets.append(current[:])
for i in range(start, len(nums)):
# Skip duplicates at the same tree depth to avoid repeated subsets
if i > start and nums[i] == nums[i - 1]:
continue
current.append(nums[i]) # Choose
backtrack(i + 1, current) # Explore
current.pop() # Un-choose
backtrack(0, [])
return subsetsReview the code by running specific example(s) and recording values (watchlist) of your code's variables along the way.
-
Input: nums = [1, 2, 2]
- After sorting, nums = [1, 2, 2].
-
backtrack(0, [])records[], then branches on index 0 (value 1). -
backtrack(1, [1])records[1], then branches on index 1 (value 2). -
backtrack(2, [1, 2])records[1, 2], then branches on index 2 (value 2). -
backtrack(3, [1, 2, 2])records[1, 2, 2]and returns (no indices left). - Back at
backtrack(1, [1]), index 2 is skipped becausenums[2] == nums[1]and2 > start— this is what prevents a second copy of[1, 2]. - Back at the top level, index 1 (value 2) branches:
[2]and[2, 2]are recorded; index 2 is skipped at the top level for the same reason. - Output: ], [1], [1, 2], [1, 2, 2], [2], [2, 2 — matches the expected output with no duplicate subsets.
-
Input: nums = []
-
backtrack(0, [])records[]and the loop body never runs. - Output: [[]]
-
Evaluate the performance of your algorithm and state any strong/weak or future potential work.
Assume N is the number of elements in nums.
-
Time Complexity:
O(N * 2^N)— there are at most2^Nsubsets, and copying each subset into the results costs up toO(N). The initial sort addsO(N log N), which is dominated. -
Space Complexity:
O(N)auxiliary space for the recursion stack and thecurrentselection (the output list of up to2^Nsubsets is not counted as auxiliary space).