🧠
Backtracking with Sorting and Early Pruning
💡 Sorting candidates allows early stopping when sum exceeds target, reducing unnecessary recursion and improving efficiency by pruning branches early.
Intuition
By sorting, once a candidate exceeds the remaining target, no further candidates need to be tried at that recursion level.
Algorithm
- Sort candidates in ascending order.
- Recursively try candidates starting from current index.
- If candidate exceeds remaining target, break loop to prune.
- Record combinations when sum equals target.
💡 Sorting helps cut down branches early, making recursion tree smaller and faster to explore.
from typing import List
def combinationSum(candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
result = []
def backtrack(start, path, total):
if total == target:
result.append(path[:])
return
for i in range(start, len(candidates)):
if candidates[i] + total > target:
break
path.append(candidates[i])
backtrack(i, path, total + candidates[i])
path.pop()
backtrack(0, [], 0)
return result
# Driver code
if __name__ == '__main__':
print(combinationSum([2,3,6,7], 7))
Line Notes
candidates.sort()Sort candidates to enable early pruning by ascending order.
result = []Initialize list to store valid combinations.
def backtrack(start, path, total):Recursive helper exploring combinations from index 'start'.
if total == target:Base case: found valid combination summing to target.
for i in range(start, len(candidates)):Iterate candidates from 'start' to allow reuse.
if candidates[i] + total > target:If adding candidate exceeds target, break to prune further exploration.
path.append(candidates[i])Choose candidate i and add to path.
backtrack(i, path, total + candidates[i])Recurse with updated sum and path, allowing reuse.
path.pop()Backtrack by removing last candidate to try next option.
backtrack(0, [], 0)Initial call to start recursion.
import java.util.*;
public class CombinationSum {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<>();
backtrack(candidates, target, 0, new ArrayList<>(), result);
return result;
}
private void backtrack(int[] candidates, int target, int start, List<Integer> path, List<List<Integer>> result) {
if (target == 0) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] > target) break;
path.add(candidates[i]);
backtrack(candidates, target - candidates[i], i, path, result);
path.remove(path.size() - 1);
}
}
public static void main(String[] args) {
CombinationSum cs = new CombinationSum();
System.out.println(cs.combinationSum(new int[]{2,3,6,7}, 7));
}
}
Line Notes
Arrays.sort(candidates);Sort candidates to enable pruning by ascending order.
List<List<Integer>> result = new ArrayList<>();Initialize list to store valid combinations.
backtrack(candidates, target, 0, new ArrayList<>(), result);Start recursion with empty path and full target.
if (target == 0) {Base case: found valid combination summing to target.
for (int i = start; i < candidates.length; i++) {Iterate candidates from 'start' to allow reuse.
if (candidates[i] > target) break;If candidate exceeds target, break to prune further exploration.
path.add(candidates[i]);Choose candidate i and add to path.
backtrack(candidates, target - candidates[i], i, path, result);Recurse with updated target and same start index.
path.remove(path.size() - 1);Backtrack by removing last candidate to try next option.
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& result) {
if (target == 0) {
result.push_back(path);
return;
}
for (int i = start; i < candidates.size(); i++) {
if (candidates[i] > target) break;
path.push_back(candidates[i]);
backtrack(candidates, target - candidates[i], i, path, result);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
vector<vector<int>> result;
vector<int> path;
backtrack(candidates, target, 0, path, result);
return result;
}
};
int main() {
Solution sol;
vector<int> candidates = {2,3,6,7};
vector<vector<int>> res = sol.combinationSum(candidates, 7);
for (auto &comb : res) {
cout << "[";
for (int i = 0; i < comb.size(); i++) {
cout << comb[i];
if (i != comb.size() - 1) cout << ",";
}
cout << "]\n";
}
return 0;
}
Line Notes
sort(candidates.begin(), candidates.end());Sort candidates to enable pruning by ascending order.
void backtrack(vector<int>& candidates, int target, int start, vector<int>& path, vector<vector<int>>& result) {Recursive helper exploring combinations from index 'start'.
if (target == 0) {Base case: found valid combination summing to target.
for (int i = start; i < candidates.size(); i++) {Iterate candidates from 'start' to allow reuse.
if (candidates[i] > target) break;If candidate exceeds target, break to prune further exploration.
path.push_back(candidates[i]);Choose candidate i and add to path.
backtrack(candidates, target - candidates[i], i, path, result);Recurse with updated target and same start index.
path.pop_back();Backtrack by removing last candidate to try next option.
function combinationSum(candidates, target) {
candidates.sort((a,b) => a - b);
const result = [];
function backtrack(start, path, total) {
if (total === target) {
result.push([...path]);
return;
}
for (let i = start; i < candidates.length; i++) {
if (candidates[i] + total > target) break;
path.push(candidates[i]);
backtrack(i, path, total + candidates[i]);
path.pop();
}
}
backtrack(0, [], 0);
return result;
}
// Test
console.log(combinationSum([2,3,6,7], 7));
Line Notes
candidates.sort((a,b) => a - b);Sort candidates to enable pruning by ascending order.
const result = [];Initialize array to store valid combinations.
function backtrack(start, path, total) {Recursive helper exploring combinations from index 'start'.
if (total === target) {Base case: found valid combination summing to target.
for (let i = start; i < candidates.length; i++) {Iterate candidates from 'start' to allow reuse.
if (candidates[i] + total > target) break;If adding candidate exceeds target, break to prune further exploration.
path.push(candidates[i]);Choose candidate i and add to path.
backtrack(i, path, total + candidates[i]);Recurse with updated sum and path.
path.pop();Backtrack by removing last candidate to try next option.
TimeImproved over brute force due to pruning but still exponential in worst case
SpaceO(T/M) recursion depth and path storage
Sorting and pruning reduce branches by stopping exploration early when candidates exceed remaining target, but worst case remains exponential if many combinations exist.
💡 For example, pruning avoids exploring candidates that can't fit, saving time especially with large candidates.
Interview Verdict: Accepted and faster than brute force
This approach is a practical improvement and often acceptable in interviews.