Back to Backtracking
Combination Sum I & II
Combination Sum I & II
🧩 Problem Statement
Combination Sum I
Given an array of distinct integers candidates and a target integer target, return a list of all unique combinations of candidates where the chosen numbers sum to target.
- The same number may be chosen from
candidatesan unlimited number of times. - Two combinations are unique if the frequency of at least one of the chosen numbers is different.
Combination Sum II
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.
- Each number in
candidatesmay only be used once in the combination. - The solution set must not contain duplicate combinations.
🔹 Example 1 (Combination Sum I):
Input:
candidates = [2,3,6,7], target = 7
Output:
[[2,2,3],[7]]
🔹 Example 2 (Combination Sum II):
Input:
candidates = [10,1,2,7,6,1,5], target = 8
Output:
[[1,1,6], [1,2,5], [1,7], [2,6]]
🔍 Approaches
1. Backtracking (Generic)
We explore the decision tree: at each step, we can either include a candidate or skip it.
Combination Sum I (Unlimited Usage)
- At index
i, if we pickcandidates[i], we stay at indexifor the next recursion (allowing reuse). - If we skip
candidates[i], we move toi + 1. - Target Reduction:
newTarget = target - candidates[i].
Combination Sum II (Single Usage + Duplicates)
- Sort the array first to handle duplicates easily.
- Use a Loop-based backtracking approach.
- Iterate from
startindex to end. - Skip Duplicates: If
i > startandcandidates[i] == candidates[i-1], continue. - If we pick
candidates[i], we move toi + 1(single usage).
⏳ Time & Space Complexity
- Time Complexity: $O(2^T)$ where T is target value (roughly). It depends on branching factor.
- Space Complexity: $O(T/min_candidate)$ for recursion stack.
🚀 Code Implementations
C++
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
// Combination Sum I
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
vector<int> path;
backtrack1(candidates, target, 0, path, res);
return res;
}
// Combination Sum II
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end()); // Required for dedup
vector<vector<int>> res;
vector<int> path;
backtrack2(candidates, target, 0, path, res);
return res;
}
private:
void backtrack1(vector<int>& candidates, int target, int start,
vector<int>& path, vector<vector<int>>& res) {
if (target == 0) {
res.push_back(path);
return;
}
if (target < 0) return;
for (int i = start; i < candidates.size(); ++i) {
path.push_back(candidates[i]);
// Stay at i for unlimited usage
backtrack1(candidates, target - candidates[i], i, path, res);
path.pop_back();
}
}
void backtrack2(vector<int>& candidates, int target, int start,
vector<int>& path, vector<vector<int>>& res) {
if (target == 0) {
res.push_back(path);
return;
}
if (target < 0) return;
for (int i = start; i < candidates.size(); ++i) {
// Skip duplicates
if (i > start && candidates[i] == candidates[i-1]) continue;
path.push_back(candidates[i]);
// Move to i + 1 for single usage
backtrack2(candidates, target - candidates[i], i + 1, path, res);
path.pop_back();
}
}
};
Python
from typing import List
class Solution:
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
res = []
self._dfs1(candidates, target, 0, [], res)
return res
def _dfs1(self, candidates, target, start, path, res):
if target == 0:
res.append(path[:])
return
if target < 0:
return
for i in range(start, len(candidates)):
path.append(candidates[i])
self._dfs1(candidates, target - candidates[i], i, path, res)
path.pop()
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
res = []
self._dfs2(candidates, target, 0, [], res)
return res
def _dfs2(self, candidates, target, start, path, res):
if target == 0:
res.append(path[:])
return
if target < 0:
return
for i in range(start, len(candidates)):
if i > start and candidates[i] == candidates[i-1]:
continue
path.append(candidates[i])
self._dfs2(candidates, target - candidates[i], i + 1, path, res)
path.pop()
Java
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
class Solution {
// Combination Sum I
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
backtrack1(candidates, target, 0, new ArrayList<>(), res);
return res;
}
private void backtrack1(int[] candidates, int target, int start,
List<Integer> path, List<List<Integer>> res) {
if (target == 0) {
res.add(new ArrayList<>(path));
return;
}
if (target < 0) return;
for (int i = start; i < candidates.length; i++) {
path.add(candidates[i]);
// Reuse same element: i
backtrack1(candidates, target - candidates[i], i, path, res);
path.remove(path.size() - 1);
}
}
// Combination Sum II
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> res = new ArrayList<>();
backtrack2(candidates, target, 0, new ArrayList<>(), res);
return res;
}
private void backtrack2(int[] candidates, int target, int start,
List<Integer> path, List<List<Integer>> res) {
if (target == 0) {
res.add(new ArrayList<>(path));
return;
}
if (target < 0) return;
for (int i = start; i < candidates.length; i++) {
if (i > start && candidates[i] == candidates[i-1]) continue;
path.add(candidates[i]);
// Next element: i + 1
backtrack2(candidates, target - candidates[i], i + 1, path, res);
path.remove(path.size() - 1);
}
}
}
🌍 Real-World Analogy
Shopping with Coins:
- I: You have an infinite supply of coin types (2, 3, 5). How to make 10? (2+2+2+2+2, 5+5, etc).
- II: You have a specific handful of coins in your pocket (2, 2, 3, 5). You can't use a coin more times than you have it.
🎯 Summary
✅ Use Index: start determines the subset of candidates available.
✅ Unlimited vs Single: Pass i vs i+1 to recursion.
✅ Dedup: Sort + i > start check.
Solution Code
O(n) TimeO(1) Space
#include <algorithm>
#include <iostream>
#include <vector>
using namespace std;
class Solution {
public:
// Combination Sum I
vector<vector<int>> combinationSum(vector<int> &candidates, int target) {
vector<vector<int>> res;
vector<int> path;
backtrack1(candidates, target, 0, path, res);
return res;
}
// Combination Sum II
vector<vector<int>> combinationSum2(vector<int> &candidates, int target) {
sort(candidates.begin(), candidates.end()); // Required for dedup
vector<vector<int>> res;
vector<int> path;
backtrack2(candidates, target, 0, path, res);
return res;
}
private:
void backtrack1(vector<int> &candidates, int target, int start,
vector<int> &path, vector<vector<int>> &res) {
if (target == 0) {
res.push_back(path);
return;
}
if (target < 0)
return;
for (int i = start; i < candidates.size(); ++i) {
path.push_back(candidates[i]);
// Stay at i for unlimited usage
backtrack1(candidates, target - candidates[i], i, path, res);
path.pop_back();
}
}
void backtrack2(vector<int> &candidates, int target, int start,
vector<int> &path, vector<vector<int>> &res) {
if (target == 0) {
res.push_back(path);
return;
}
if (target < 0)
return;
for (int i = start; i < candidates.size(); ++i) {
// Skip duplicates
if (i > start && candidates[i] == candidates[i - 1])
continue;
path.push_back(candidates[i]);
// Move to i + 1 for single usage
backtrack2(candidates, target - candidates[i], i + 1, path, res);
path.pop_back();
}
}
};
void printRes(const vector<vector<int>> &res) {
cout << "[";
for (size_t i = 0; i < res.size(); ++i) {
cout << "[";
for (size_t j = 0; j < res[i].size(); ++j) {
cout << res[i][j] << (j < res[i].size() - 1 ? "," : "");
}
cout << "]" << (i < res.size() - 1 ? "," : "");
}
cout << "]" << endl;
}
int main() {
Solution sol;
vector<int> c1 = {2, 3, 6, 7};
int t1 = 7;
cout << "Combination Sum I [2,3,6,7] t=7: " << endl;
printRes(sol.combinationSum(c1, t1));
vector<int> c2 = {10, 1, 2, 7, 6, 1, 5};
int t2 = 8;
cout << "Combination Sum II [10,1,2,7,6,1,5] t=8: " << endl;
printRes(sol.combinationSum2(c2, t2));
return 0;
}
SYSTEM STABLEUTF-8 • STATIC_RENDER