Tuesday, March 28, 2017

39. Combination Sum

六刷 06/2022
Version #1 DFS with backtracking

Let N be the number of candidates, T be the target value, and M be the minimal value among the candidates.

  • Time Complexity: \mathcal{O}(N^{\frac{T}{M}+1})

    • As we illustrated before, the execution of the backtracking is unfolded as a DFS traversal in a n-ary tree. The total number of steps during the backtracking would be the number of nodes in the tree.

    • At each node, it takes a constant time to process, except the leaf nodes which could take a linear time to make a copy of combination. So we can say that the time complexity is linear to the number of nodes of the execution tree.

    • Here we provide a loose upper bound on the number of nodes.

      • First of all, the fan-out of each node would be bounded to Ni.e. the total number of candidates.

      • The maximal depth of the tree, would be \frac{T}{M}, where we keep on adding the smallest element to the combination.

      • As we know, the maximal number of nodes in N-ary tree of \frac{T}{M} height would be N^{\frac{T}{M}+1}.

    • Note that, the actual number of nodes in the execution tree would be much smaller than the upper bound, since the fan-out of the nodes are decreasing level by level.

  • Space Complexity: \mathcal{O}(\frac{T}{M})

    • We implement the algorithm in recursion, which consumes some additional memory in the function call stack.

    • The number of recursive calls can pile up to \frac{T}{M}, where we keep on adding the smallest element to the combination. As a result, the space overhead of the recursion is \mathcal{O}(\frac{T}{M}).

    • In addition, we keep a combination of numbers during the execution, which requires at most \mathcal{O}(\frac{T}{M}) space as well.

    • To sum up, the total space complexity of the algorithm would be \mathcal{O}(\frac{T}{M}).

    • Note that, we did not take into the account the space used to hold the final results for the space complexity.

Runtime: 8 ms, faster than 36.01% of Java online submissions for Combination Sum.
Memory Usage: 45.8 MB, less than 20.70% of Java online submissions for Combination Sum.
class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        if (candidates == null || candidates.length == 0) {
            return result;
        }
        dfs(candidates, target, 0, new ArrayList<>(), result);
        return result;
    }
    
    private void dfs(int[] candidates, int target, int startIndex, List<Integer> path, List<List<Integer>> result) {
        if (target == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (target < 0) {
            return;
        }
        for (int i = startIndex; i < candidates.length; i++) {
            path.add(candidates[i]);
            dfs(candidates, target - candidates[i], i, path, result);
            path.remove(path.size() - 1);
        }
    }
}


Given a set of candidate numbers (C(without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.


五刷
这一遍的思路和4刷不一样,导致代码有一点复杂
思路是every layer add a number [0 - max] times
每一层选择一个数字,加多次
四刷的思路是每一层是当前index加或者不加,如果一个数字加多次是多层
看了discussion还是4刷那种一般的backtracking比较普遍,代码也更straightforward

Runtime: 3 ms, faster than 75.67% of Java online submissions for Combination Sum.
Memory Usage: 38.8 MB, less than 97.25% of Java online submissions for Combination Sum.

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        helper(0, candidates, target, new ArrayList<>(), result);
        return result;
    }
    
    private void helper(int index, int[] candidates, int target, List<Integer> path, List<List<Integer>> result) {
        if (target == 0) {
            result.add(new ArrayList<>(path));
            return;
        }
        if (index == candidates.length) {
            return;
        }
        int len = path.size();
        for (int i = 0; i * candidates[index] <= target; i++) {
            if (i > 0) { // can be skipped
                path.add(candidates[index]);
            }
            helper(index + 1, candidates, target - i * candidates[index], path, result);
        }
        while (path.size() > len) {
            path.remove(path.size() - 1);
        }
    }
}

4TH TRY
 84.42 %

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
Arrays.sort(candidates);
List<List<Integer>> result = new ArrayList<>();
dfs(candidates, candidates.length - 1, new ArrayList<>(), result, target);
return result;
}
private void dfs(int[] candidates, int index, List<Integer> path, List<List<Integer>> result, int target) {
if (target <= 0) {
if (target == 0) {
result.add(new ArrayList<>(path));
}
return;
}

for (int i = index; i >= 0; i--) {
path.add(candidates[i]);
dfs(candidates, i, path, result, target - candidates[i]);
path.remove(path.size() - 1);
}
}
}

三刷
写了这么多遍还是在index上有问题啊。。。疯了
跟二刷写的一样
一刷不懂为啥要去重,可能原来没有duplicates这个条件
94.37 %

//for every layer, choose a number from candidates and subtract it from the target
//prunning-try to add from smaller to larger
//once the number is larger than the target, we can break the current layer(positive integers)
public class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<>();
        if (candidates == null || candidates.length == 0 || target <= 0) return result;
        //O(nlogn)
        Arrays.sort(candidates);
        dfs(candidates, target, 0, new ArrayList<Integer>(), result);
        return result;
    }
    private void dfs(int[] candidates, int target, int startIndex, List<Integer> path, List<List<Integer>> result) {
        if (target == 0) {
            result.add(new ArrayList<Integer>(path));
            return;
        }
        int curr = 0;
        for (int i = startIndex; i < candidates.length; i++) {
            curr = candidates[i];
            //Ensures that {target} will never be less than 0
            if (curr > target) break;
            path.add(curr);
            //can add the same numbers
            dfs(candidates, target - curr, i, path, result);
            path.remove(path.size() - 1);
        }
    }
}

一刷
It is worth mentioning that the removeDuplicates method uses in place duplication remove algorithm.

public class Solution {
    /**
     * @param candidates: A list of integers
     * @param target:An integer
     * @return: A list of lists of integers
     */
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // write your code here
        List<List<Integer>> result = new ArrayList<>();
        if (candidates == null || candidates.length == 0) {
            return result;
        }
        int[] nums = removeDuplicates(candidates);
        dfs(nums, target, 0, new ArrayList<Integer>(), result);
        return result;
   
    }

    //call dfs recursively
    //add path to result list
    public void dfs(int[] nums, int target, int startIndex,
                    List<Integer> path, List<List<Integer>> result) {
                        if (target == 0) {
                            result.add(new ArrayList<Integer>(path));
                            return;
                        }
                   
                        for (int i = startIndex; i < nums.length; i++) {
                            if (nums[i] > target) return;
                            path.add(nums[i]);
                            dfs(nums, target - nums[i], i, path, result);
                            path.remove(path.size() - 1);
                        }
                    }

    //1.sort
    //2.remove duplicates
    public int[] removeDuplicates(int[] candidates) {
        Arrays.sort(candidates);
        int index = 0;
        for (int i = 0; i < candidates.length; i++) {
            if (candidates[i] != candidates[index]) {
                candidates[++index] = candidates[i];
            }
        }
        int[] nums = new int[index + 1];
        for (int j = 0; j < nums.length; j++) {
            nums[j] = candidates[j];
        }
        return nums;
    }
}





二刷
73.69%

public class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        if (candidates == null || candidates.length == 0 || target <= 0) return result;
        Arrays.sort(candidates);
        helper(candidates, target, 0, new ArrayList<Integer>(), result);
        return result;
    }
    public void helper(int[] candidates, int target, int startIndex, List<Integer> path, List<List<Integer>> result) {
        if (target == 0) {
            result.add(new ArrayList<Integer>(path));
            return;
        }
   
        for (int index = startIndex; index < candidates.length; index++) {
            if (candidates[index] > target) return;
            path.add(candidates[index]);
            helper(candidates, target - candidates[index], index, path, result);
            path.remove(path.size() - 1);
        }
    }
}

No comments:

Post a Comment