Reputation: 113
The question is to find all of the unique combinations in a sorted array that sum to a given target value. For this example, let's say candidates=[2,3,6,7] and target=7. I watched some videos and I now understand the general algorithm of how to solve this problem. I also looked at a solution, but I'm having some trouble visualizing the recursive tree/each recursive call in the function.
var combinationSum = function(candidates, target) {
candidates.sort((a,b) => a-b)
let result = []
combSum(candidates, target, [], result, 0)
return result
};
function combSum(candidates, target, current, result, idx) {
let n
for (let i = idx; i < candidates.length; i++) {
n = candidates[i]
current.push(n)
if (n === target) {
result.push([...current])
} else if (target > n) {
combSum(candidates, target-n, [...current], result, i)
}
current.pop()
}
}
I know the first step is to try combinations starting with the first value in the array which is 2. So the function will try [2], then [2,2], then [2,2,2], and at this point the target is 1 which is less than n, so it skips both if statements and pops the last 2 off. Does the pop() method implicitly return to the previous function call on the call stack? It would return a value of 2 which never actually gets used, is this correct? There's no base case which is throwing me off.
Also, I know that since the array is sorted, we should know that if something like [2,2,3,3] doesn't work, then any other combination starting with the prefix of [2,2,3] also will not work. I don't see how the code addresses this though - how does it know to 'skip' these combinations? Like [2,2,3,6], etc.
EDIT: It's really late, I realized in my original post that I was looking at a different target value which was adding to my confusion... I fixed my post to reflect this. Sorry!!
Upvotes: 2
Views: 260
Reputation: 17238
This answer does not address the visualization aspect but limits itself to your questions on specific details.
Preliminaries
The recursion is based on the idea that at any step in the incremental construction of a combination that sums to a target, the original problem needs to be solved for the difference of the original target and the sum of the current partial combination using the same set of candidates.
The parameters to combSum
carry the following meaning:
candidates
: The pool of numbers to pick from (ordered array)target
: The number to compose (integer)current
: The partial combination currently being completed (ordered array)result
: The combinations found so faridx
: Minimum index of candidates
element to be used in call.Conceptually candidates
and idx
fold into a single actual parameter candidates.slice(i)
.
There are 2 invariants in the recursion:
current
representing the partially constructed combination currently being completed are non-decreasing.current
that were present
upon invocation.The ordering of the candidates helps to avoid repeated constructions of the same sequence. Remember that in each recursive call the effective set of candidate elements is candidates.slice(i)
with i
non-decreasing and in every recursion level's loop, this level's i
start value starts with the current value of i
from the parent level.
Note that this only works if in candidates
there are no duplicates of numbers that show up in a result combination, otherwise subsequences starting with this number will be computed multiple times producing several identical results ( Try combinationSum([1,4], 4)
and combinationSum([1,1,4], 4)
; being precise, this will not occur if the multiplicity in candidates
would equal the multiplicity in each result ... try combinationSum([2,2,5], 9)
and combinationSum([2,5], 9)
)
1. Recursion base
The recursion base is the case n >= target
...
2. Skipping 'impossible' prefixes
... If n === target
, the current partial combination is completed and added to the results. If n > target
the current partial combination cannot be successfully completed (candidate numbers will only get bigger and the current one is already too large). However, the code does not cater for this condition ( it could with a if (n > target) break;
statement at the end of the loop ).
3. Implicit return
current.pop()
restores the partial combination the current invocation of combSum
started with. This is its purpose. While technically pop
returns some value, but this value has already been used - it is the top element of current
at the site of the recursive call!
Upvotes: 1