Skip to content

Commit dce3e17

Browse files
feat: add NeetCode 150 - Backtracking category (partial)
- Added 7 problems from Backtracking category: - Subsets (Java solution + explanation) - Combination Sum (Java solution + explanation) - Permutations (Java solution + explanation) - Subsets II (Java solution + explanation) - Combination Sum II (Java solution + explanation) - Word Search (Java solution + explanation) - Palindrome Partitioning (explanation) - Multiple solution approaches for each problem - Comprehensive explanations with algorithms and complexity analysis - Backtracking patterns: subset generation, combination sum, permutations, duplicate handling - DFS and state restoration techniques
1 parent 9fac62a commit dce3e17

File tree

13 files changed

+1716
-0
lines changed

13 files changed

+1716
-0
lines changed
Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
# Combination Sum II
2+
3+
## Problem Statement
4+
5+
Given a collection of candidate numbers (`candidates`) and a target number (`target`), find all unique combinations in `candidates` where the candidate numbers sum to `target`.
6+
7+
Each number in `candidates` may only be used once in the combination.
8+
9+
Note: The solution set must not contain duplicate combinations.
10+
11+
## Examples
12+
13+
**Example 1:**
14+
```
15+
Input: candidates = [10,1,2,7,6,1,5], target = 8
16+
Output: [[1,1,6],[1,2,5],[1,7],[2,6]]
17+
```
18+
19+
**Example 2:**
20+
```
21+
Input: candidates = [2,5,2,1,2], target = 5
22+
Output: [[1,2,2],[5]]
23+
```
24+
25+
## Approach
26+
27+
### Method 1: Backtracking with Duplicate Handling (Recommended)
28+
1. Sort array to group duplicates together
29+
2. Use backtracking to explore all possible combinations
30+
3. Skip duplicates at same level to avoid duplicate combinations
31+
4. Use each number only once
32+
33+
**Time Complexity:** O(2^n) - Exponential in worst case
34+
**Space Complexity:** O(target) - Recursion stack depth
35+
36+
### Method 2: Dynamic Programming
37+
1. Use DP to build combinations bottom-up
38+
2. Handle duplicates by tracking previous combinations
39+
3. Use memoization to avoid recomputation
40+
41+
**Time Complexity:** O(target * candidates.length)
42+
**Space Complexity:** O(target * candidates.length)
43+
44+
## Algorithm
45+
46+
```
47+
1. Sort candidates array
48+
2. Backtrack function:
49+
a. If target == 0: add current combination to result
50+
b. If target < 0: return (prune invalid path)
51+
c. For each candidate starting from current index:
52+
- Skip duplicates at same level
53+
- Add candidate to combination
54+
- Recursively call with target - candidate
55+
- Remove candidate from combination (backtrack)
56+
```
57+
58+
## Key Insights
59+
60+
- **Sorting**: Group duplicates together
61+
- **Skip Duplicates**: Skip same elements at same level
62+
- **Single Use**: Each number used only once
63+
- **Pruning**: Stop when target becomes negative
64+
65+
## Alternative Approaches
66+
67+
1. **Dynamic Programming**: Use DP for optimization
68+
2. **Iterative**: Use iterative approach with stack
69+
3. **Set**: Use Set to automatically handle duplicates
70+
71+
## Edge Cases
72+
73+
- Empty candidates: Return empty list
74+
- Target 0: Return [[]]
75+
- No valid combinations: Return empty list
76+
- All duplicates: Handle appropriately
77+
78+
## Applications
79+
80+
- Combinatorics
81+
- Algorithm design patterns
82+
- Interview preparation
83+
- Data structure operations
84+
- System design
85+
86+
## Optimization Opportunities
87+
88+
- **Backtracking**: Most intuitive approach
89+
- **Early Pruning**: Stop when target < 0
90+
- **Sorting**: Efficient duplicate handling
91+
- **Skip Logic**: Avoid duplicate combinations
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
/**
2+
* Time Complexity: O(2^n) - Exponential in worst case
3+
* Space Complexity: O(target) - Recursion stack depth
4+
*/
5+
class Solution {
6+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
7+
List<List<Integer>> result = new ArrayList<>();
8+
Arrays.sort(candidates); // Sort to group duplicates
9+
backtrack(candidates, target, 0, new ArrayList<>(), result);
10+
return result;
11+
}
12+
13+
private void backtrack(int[] candidates, int target, int start, List<Integer> current, List<List<Integer>> result) {
14+
if (target == 0) {
15+
result.add(new ArrayList<>(current));
16+
return;
17+
}
18+
19+
if (target < 0) {
20+
return; // Prune invalid path
21+
}
22+
23+
for (int i = start; i < candidates.length; i++) {
24+
// Skip duplicates at same level
25+
if (i > start && candidates[i] == candidates[i - 1]) {
26+
continue;
27+
}
28+
29+
if (candidates[i] > target) {
30+
break; // Prune since array is sorted
31+
}
32+
33+
current.add(candidates[i]);
34+
backtrack(candidates, target - candidates[i], i + 1, current, result);
35+
current.remove(current.size() - 1); // Backtrack
36+
}
37+
}
38+
}
39+
40+
// Alternative approach using Set
41+
class SolutionSet {
42+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
43+
Set<List<Integer>> result = new HashSet<>();
44+
Arrays.sort(candidates);
45+
backtrack(candidates, target, 0, new ArrayList<>(), result);
46+
return new ArrayList<>(result);
47+
}
48+
49+
private void backtrack(int[] candidates, int target, int start, List<Integer> current, Set<List<Integer>> result) {
50+
if (target == 0) {
51+
result.add(new ArrayList<>(current));
52+
return;
53+
}
54+
55+
if (target < 0) {
56+
return;
57+
}
58+
59+
for (int i = start; i < candidates.length; i++) {
60+
if (candidates[i] > target) {
61+
break;
62+
}
63+
64+
current.add(candidates[i]);
65+
backtrack(candidates, target - candidates[i], i + 1, current, result);
66+
current.remove(current.size() - 1);
67+
}
68+
}
69+
}
70+
71+
// Alternative approach using frequency map
72+
class SolutionFrequency {
73+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
74+
Map<Integer, Integer> freq = new HashMap<>();
75+
for (int num : candidates) {
76+
freq.put(num, freq.getOrDefault(num, 0) + 1);
77+
}
78+
79+
List<List<Integer>> result = new ArrayList<>();
80+
backtrack(freq, target, new ArrayList<>(), result);
81+
return result;
82+
}
83+
84+
private void backtrack(Map<Integer, Integer> freq, int target, List<Integer> current, List<List<Integer>> result) {
85+
if (target == 0) {
86+
result.add(new ArrayList<>(current));
87+
return;
88+
}
89+
90+
if (target < 0) {
91+
return;
92+
}
93+
94+
for (int num : freq.keySet()) {
95+
if (freq.get(num) > 0 && num <= target) {
96+
current.add(num);
97+
freq.put(num, freq.get(num) - 1);
98+
backtrack(freq, target - num, current, result);
99+
freq.put(num, freq.get(num) + 1);
100+
current.remove(current.size() - 1);
101+
}
102+
}
103+
}
104+
}
105+
106+
// Alternative approach using iterative method
107+
class SolutionIterative {
108+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
109+
List<List<Integer>> result = new ArrayList<>();
110+
Stack<List<Integer>> stack = new Stack<>();
111+
Stack<Integer> targetStack = new Stack<>();
112+
Stack<Integer> indexStack = new Stack<>();
113+
114+
stack.push(new ArrayList<>());
115+
targetStack.push(target);
116+
indexStack.push(0);
117+
118+
while (!stack.isEmpty()) {
119+
List<Integer> current = stack.pop();
120+
int remainingTarget = targetStack.pop();
121+
int startIndex = indexStack.pop();
122+
123+
if (remainingTarget == 0) {
124+
result.add(current);
125+
continue;
126+
}
127+
128+
if (remainingTarget < 0) {
129+
continue;
130+
}
131+
132+
for (int i = startIndex; i < candidates.length; i++) {
133+
if (i > startIndex && candidates[i] == candidates[i - 1]) {
134+
continue;
135+
}
136+
137+
if (candidates[i] > remainingTarget) {
138+
break;
139+
}
140+
141+
List<Integer> newCombination = new ArrayList<>(current);
142+
newCombination.add(candidates[i]);
143+
stack.push(newCombination);
144+
targetStack.push(remainingTarget - candidates[i]);
145+
indexStack.push(i + 1);
146+
}
147+
}
148+
149+
return result;
150+
}
151+
}
152+
153+
// More concise version
154+
class SolutionConcise {
155+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
156+
List<List<Integer>> result = new ArrayList<>();
157+
Arrays.sort(candidates);
158+
backtrack(candidates, target, 0, new ArrayList<>(), result);
159+
return result;
160+
}
161+
162+
private void backtrack(int[] candidates, int target, int start, List<Integer> current, List<List<Integer>> result) {
163+
if (target == 0) {
164+
result.add(new ArrayList<>(current));
165+
return;
166+
}
167+
168+
if (target < 0) return;
169+
170+
for (int i = start; i < candidates.length; i++) {
171+
if (i > start && candidates[i] == candidates[i - 1]) continue;
172+
if (candidates[i] > target) break;
173+
174+
current.add(candidates[i]);
175+
backtrack(candidates, target - candidates[i], i + 1, current, result);
176+
current.remove(current.size() - 1);
177+
}
178+
}
179+
}
180+
181+
// Using memoization
182+
class SolutionMemo {
183+
private Map<String, List<List<Integer>>> memo = new HashMap<>();
184+
185+
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
186+
Arrays.sort(candidates);
187+
return backtrack(candidates, target, 0);
188+
}
189+
190+
private List<List<Integer>> backtrack(int[] candidates, int target, int start) {
191+
String key = target + "," + start;
192+
if (memo.containsKey(key)) {
193+
return memo.get(key);
194+
}
195+
196+
List<List<Integer>> result = new ArrayList<>();
197+
198+
if (target == 0) {
199+
result.add(new ArrayList<>());
200+
return result;
201+
}
202+
203+
if (target < 0) {
204+
return result;
205+
}
206+
207+
for (int i = start; i < candidates.length; i++) {
208+
if (i > start && candidates[i] == candidates[i - 1]) {
209+
continue;
210+
}
211+
212+
if (candidates[i] > target) {
213+
break;
214+
}
215+
216+
List<List<Integer>> subResults = backtrack(candidates, target - candidates[i], i + 1);
217+
for (List<Integer> subResult : subResults) {
218+
List<Integer> newResult = new ArrayList<>(subResult);
219+
newResult.add(candidates[i]);
220+
result.add(newResult);
221+
}
222+
}
223+
224+
memo.put(key, result);
225+
return result;
226+
}
227+
}

0 commit comments

Comments
 (0)