39. Combination Sum

Combination Sum

Combination Sum


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. You may return the combinations in any order.

The same number may be chosen  candidates an unlimited number of times. Two combinations are unique if the frequency of at least one of the chosen numbers is different.

The test cases are generated such that the number of unique combinations that sum up to target is less than 150 combinations for the given input.

 

Example 1:

Input: candidates = [2,3,6,7], target = 7
Output: [[2,2,3],[7]]

Explanation:
2 and 3 are candidates, and 2 + 2 + 3 = 7. Note that 2 can be used multiple times.
7 is a candidate, and 7 = 7.
These are the only two combinations.


Example 2:

Input: candidates = [2,3,5], target = 8
Output: [[2,2,2,2],[2,3,3],[3,5]]


Example 3:

Input: candidates = [2], target = 1
Output: []

 

Constraints:

  • 1 <= candidates.length <= 30
  • 2 <= candidates[i] <= 40
  • All elements of candidates are distinct.
  • 1 <= target <= 40

Approach) Brute Force

var combinationSum = function(candidates, target) { 
    var arr = [];
    var result = [];
    recursion(candidates, target, "", arr,result);
    return result;
};
var recursion = function(candidates, target, acc, arr, result) { 
   
    if(target == 0) {
        var str = acc.substring(1,acc.length);
        var temp = str.split(",")
        temp = temp.sort((a,b)=>{return a-b});
        str = temp.join(","); 
        if(!arr.includes(str)) {
            arr.push(str);
            var arr = [];
            for(let i=0; i < temp.length; i++) {
                arr.push(Number(temp[i]));
            }
            result.push(arr);
        }
        return ;
    }
    
    for(let i=0; i < candidates.length; i++) {
        if(target>0) {
            recursion(candidates, target - candidates[i], acc+","+candidates[i],arr,result);
        }
    }
}

Approach) Depth First Search (DFS)


function combinationSum(candidates, target) {
    let res = [];

    const dfs = (curCandidates, curTarget, value) => {
        if( curTarget == 0)  res.push(value);
        if(curTarget <= 0 ) return;
        for(let g=0; g<curCandidates.length; g++){
            dfs(curCandidates.slice(g), curTarget-curCandidates[g], [...value, curCandidates[g] ] );  
        }
    }
    dfs(candidates, target, []);
    return res;

}

Approach) Depth First Search (DFS) + Sort

var combinationSum = function (candidates, target) {
    if (!candidates || candidates.length == 0) return [];
    let res = [];
    candidates.sort((a, b) => a - b); //prepare prefix sum
    var helper = function (curSum, cur, index) {
        if (curSum == target) {
            res.push([...cur]);
            return;
        }
        for (let i = index; i < candidates.length; i++) {
            if (curSum > target) return; //prune no valid then pop
            cur.push(candidates[i]);
            helper(curSum + candidates[i], cur, i); //can se duplicate ele
            console.log("index: ", index)
            console.log("i: ", i)
            cur.pop();
        }
    }
    helper(0, [], 0);
    return res;
};


Approach) Unbounded Knapsack Problem

One could think of this as a variant of the knapsack problem, with the added requirement of recording all possible combinations that achieve the target. Here we use a recursive approach, continually pushing on different candidate values until either we go over our target, or we find a valid combination. 
We prevent duplicates by having a start value i, and only consider values within the array less than i. In an array like [2, 3, 5, 7], we allow [2, 2, 3] but prevent [3, 2, 2] using this strategy. 
The last key point is that we return tempList.slice() instead of just tempList. This is because for javascript, pushing an array into another array does not push a copy, but rather a pointer to the actual array. 
This would mean the push/pop methods would still affect the tempList array within the return list, which we want to avoid. I would highly recommend issac3's solution for a thorough understanding of backtracking algorithms.


var combinationSum = function(candidates, target) {
    let list = []
    helper(list, [], candidates, target, 0)
    return list
};

let helper = function(list, tempList, candidates, target, start) {
    if (target < 0) return
    else if (target == 0) list.push(tempList.slice())
    else {
        for (let i = start; i < candidates.length; i++) {
            tempList.push(candidates[i])
            helper(list, tempList, candidates, target - candidates[i], i)
            tempList.pop()
        }
    }
}

Approach) Recursion

var combinationSum = function(candidates, target) {
    if(!candidates.length) {
        return [];
    }

    let arr1 = [];
    if(target>candidates[0]) {
        arr1 = combinationSum(candidates, target-candidates[0]);
        if(arr1.length) {
            arr1.forEach(v=>v.unshift(candidates[0]));
        }
    }else if(target==candidates[0]) {
        arr1 = [[candidates[0]]];
    }

    let arr2 = combinationSum(candidates.slice(1), target);
    return arr1.concat(arr2);
};

Approach) Short Recursion

Idea
Walk through each number in the candidates by index.

Base case
if t is 0, meaning the sum is equal to target, return
if t is less than 0, return

Recursion
Use all the possible num of candidates starting from idx to end. idx is needed to ensure we don't have duplicate result. For example, without idx we would get [2, 2, 3] and [3, 2, 2].

Note
Sorting is optionally here since the problem states they're all distinct numbers


var combinationSum = function(candidates, target) {
 
    const result = [];
    const helper = (t, arr, idx) => {
        
        if(t === 0) return result.push(arr);
        else if (t < 0) return;
        
        for(let i = idx; i < candidates.length; i++) {
            const num = candidates[i];
            helper(t - num, [...arr, num], i);
        }
        
    };
    
    helper(target, [], 0);
    
    return result;
    
};

Approach) Dynamic Programming

const combinationSum = function(c, t) {
    let dp = buildDPTable(t); 
    
    c.forEach(candidate => { 
        for (let num = 1; num <= t; num++) {
            if (candidate <= num) {
                let prevComb = num - candidate; 
                dp[prevComb].forEach(comb => {
                    let dup = [...comb];
                    dup.push(candidate);
                    dp[num].push(dup);
                });
            };
        };  
    });
    
    return dp[dp.length-1];
};

const buildDPTable = function(size) {
    let dp = new Array(size+1);
    for (let i = 0; i < dp.length; i++) {
        dp[i] = [];
    }; 
    dp[0] = [[]];
    return dp;
}

Conclusion


That’s all folks! In this post, we solved LeetCode problem #39. Combination Sum

I hope you have enjoyed this post. Feel free to share your thoughts on this.

You can find the complete source code on my GitHub repository. If you like what you learn. feel free to fork 🔪 and star ⭐ it.


In this blog, I have tried to collect & present the most important points to consider when improving Data structure and logic, feel free to add, edit, comment, or ask. For more information please reach me here
Happy coding!



Comments

Popular Post