Leetcode 39 Combination Sum
Source: Dev.to
Problem Statement
Given an array of distinct integers candidates and a target integer target, return all unique combinations of candidates where the chosen numbers sum to target.
You may use each candidate an unlimited number of times. Two combinations are considered different if the frequency of at least one number differs.
Example:
[2,3,3],[3,2,3], and[3,3,2]represent the same unique combination.
Example
Input
candidates = [2,3,5]
target = 8
Output
[[2,2,2,2], [2,3,3], [3,5]]
Mathematical Formulation
The problem can be expressed as finding non‑negative integers x, y, z such that
2·x + 3·y + 5·z = 8
Solution
class Solution:
def combinationSum(self, candidates, target):
results = []
def backtrack(remain, comb, start):
if remain == 0:
# found a valid combination
results.append(list(comb)) # make a shallow copy
return
if remain < 0:
# exceeded the sum, stop exploring this path
return
for i in range(start, len(candidates)):
# choose the candidate
comb.append(candidates[i])
# explore further with the same start index (allow reuse)
backtrack(remain - candidates[i], comb, i)
# backtrack
comb.pop()
backtrack(target, [], 0)
return results
Common Pitfalls
| Pitfall | Why it Happens | Fix |
|---|---|---|
results.append(comb) | Appends a reference to the mutable list comb; later modifications corrupt stored results. | Use results.append(list(comb)) to store a shallow copy. |
Looping over all candidates each recursion: for i in range(len(candidates)) | Allows permutations of the same combination (e.g., [2,3] and [3,2]). | Loop from the current start index: for i in range(start, len(candidates)). |
Not handling remain < 0 | Leads to unnecessary recursion after the sum is exceeded. | Return immediately when remain < 0. |
Depth‑First Search Walkthrough
The recursion tree for the example (candidates = [2,3,5], target = 8) is illustrated below (each node shows i – index, v – value, remain, and start).
root (target: 8)
└── [i:0, v:2] remain: 6, start: 0
├── [i:0, v:2] remain: 4, start: 0
│ ├── [i:0, v:2] remain: 2, start: 0
│ │ ├── [i:0, v:2] remain: 0, start: 0 ── Success → [2,2,2,2]
│ │ ├── [i:1, v:3] remain: -1 ── Fail
│ │ └── [i:2, v:5] remain: -3 ── Fail
│ ├── [i:1, v:3] remain: 1, start: 1
│ │ ├── [i:1, v:3] remain: -2 ── Fail
│ │ └── [i:2, v:5] remain: -4 ── Fail
│ └── [i:2, v:5] remain: -1 ── Fail
├── [i:1, v:3] remain: 3, start: 1
│ ├── [i:1, v:3] remain: 0 ── Success → [2,3,3]
│ └── [i:2, v:5] remain: -2 ── Fail
└── [i:2, v:5] remain: 1, start: 2
└── [i:2, v:5] remain: -4 ── Fail
└── [i:1, v:3] remain: 5, start: 1
├── [i:1, v:3] remain: 2, start: 1
│ ├── [i:1, v:3] remain: -1 ── Fail
│ └── [i:2, v:5] remain: -3 ── Fail
└── [i:2, v:5] remain: 0 ── Success → [3,5]
└── [i:2, v:5] remain: 3, start: 2
└── [i:2, v:5] remain: -2 ── Fail
Reference Table
| Current Path | Remain | Start | i (Index) | Candidates[i] | Result |
|---|---|---|---|---|---|
[2] | 6 | 0 | 0 | 2 | Continue |
[2,2] | 4 | 0 | 0 | 2 | Continue |
[2,2,2] | 2 | 0 | 0 | 2 | Continue |
[2,2,2,2] | 0 | 0 | 0 | 2 | Success (8) |
[2,2,2,3] | -1 | 0 | 1 | 3 | Fail |
[2,2,2,5] | -3 | 0 | 2 | 5 | Fail |
[2,2,3] | 1 | 0 | 1 | 3 | Continue |
[2,2,3,3] | -2 | 1 | 1 | 3 | Fail |
[2,2,3,5] | -4 | 1 | 2 | 5 | Fail |
[2,2,5] | -1 | 0 | 2 | 5 | Fail |
[2,3] | 3 | 0 | 1 | 3 | Continue |
[2,3,3] | 0 | 1 | 1 | 3 | Success (8) |
[2,3,5] | -2 | 1 | 2 | 5 | Fail |
[2,5] | 1 | 0 | 2 | 5 | Continue |
[2,5,5] | -4 | 2 | 2 | 5 | Fail |
[3] | 5 | 1 | 1 | 3 | Continue |
[3,3] | 2 | 1 | 1 | 3 | Continue |
[3,3,3] | -1 | 1 | 1 | 3 | Fail |
[3,3,5] | -3 | 1 | 2 | 5 | Fail |
[3,5] | 0 | 1 | 2 | 5 | Success (8) |
[5] | 3 | 2 | 2 | 5 | Continue |
[5,5] | -2 | 2 | 2 | 5 | Fail |