1ms Java DP Solution with Detailed Explanation


  • 159
    F

    Think about the recurrence relation first. How does the # of combinations of the target related to the # of combinations of numbers that are smaller than the target?

    So we know that target is the sum of numbers in the array. Imagine we only need one more number to reach target, this number can be any one in the array, right? So the # of combinations of target, comb[target] = sum(comb[target - nums[i]]), where 0 <= i < nums.length, and target >= nums[i].

    In the example given, we can actually find the # of combinations of 4 with the # of combinations of 3(4 - 1), 2(4- 2) and 1(4 - 3). As a result, comb[4] = comb[4-1] + comb[4-2] + comb[4-3] = comb[3] + comb[2] + comb[1].

    Then think about the base case. Since if the target is 0, there is only one way to get zero, which is using 0, we can set comb[0] = 1.

    EDIT: The problem says that target is a positive integer that makes me feel it's unclear to put it in the above way. Since target == 0 only happens when in the previous call, target = nums[i], we know that this is the only combination in this case, so we return 1.

    Now we can come up with at least a recursive solution.

    public int combinationSum4(int[] nums, int target) {
        if (target == 0) {
            return 1;
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            if (target >= nums[i]) {
                res += combinationSum4(nums, target - nums[i]);
            }
        }
        return res;
    }
    

    Now for a DP solution, we just need to figure out a way to store the intermediate results, to avoid the same combination sum being calculated many times. We can use an array to save those results, and check if there is already a result before calculation. We can fill the array with -1 to indicate that the result hasn't been calculated yet. 0 is not a good choice because it means there is no combination sum for the target.

    private int[] dp;
    
    public int combinationSum4(int[] nums, int target) {
        dp = new int[target + 1];
        Arrays.fill(dp, -1);
        dp[0] = 1;
        return helper(nums, target);
    }
    
    private int helper(int[] nums, int target) {
        if (dp[target] != -1) {
            return dp[target];
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            if (target >= nums[i]) {
                res += helper(nums, target - nums[i]);
            }
        }
        dp[target] = res;
        return res;
    }
    

    EDIT: The above solution is top-down. How about a bottom-up one?

    public int combinationSum4(int[] nums, int target) {
        int[] comb = new int[target + 1];
        comb[0] = 1;
        for (int i = 1; i < comb.length; i++) {
            for (int j = 0; j < nums.length; j++) {
                if (i - nums[j] >= 0) {
                    comb[i] += comb[i - nums[j]];
                }
            }
        }
        return comb[target];
    }
    

  • 2

    @FreeTymeKiyan said in 1ms Java DP Solution with Detailed Explanation:

    public int combinationSum4() {
        if (target == 0) {
            return 1;
        }
        int res = 0;
        for (int i = 0; i < nums.length; i++) {
            if (target >= nums[i]) {
                res += combinationSum4(nums, target - nums[i]);
            }
        }
        return res;
    }
    

    Why not removing that if statement inside the for loop and substitute with a if (target <= 0) return not target; base case? The code would be clearer and shorter.


  • 0
    F

    @agave Agreed. Thanks for the suggestion.


  • 3
    M

    Why the top-down solution is much faster than the bottom-up solution (1ms vs 7ms)


  • 0
    M

    @metalsolid1 I have the same question. In fact, i was expecting the bottom-up to be faster.


  • 0

    @mishrasonu1993 Because there are just 17 test cases?


  • 17
    F

    @metalsolid1 @mishrasonu1993
    Good question guys! Actually if you look closely at the implementation of bottom-up approach, you will notice that it may have more "intermediate steps" than the top-down one.

    For example, given nums = [100], target = 100, the top-down approach will get the result directly, while the bottom-up has to build comb[1] all the way to comb[100].

    Is it possible to calculate only necessary intermediate steps in bottom-up approach? It's possible because you can find the next smallest sum with the nums given. But I think it's hard enough to be another independent problem.


  • 0
    A

    c code, "0ms"

    int combinationSum4(int* nums, int numsSize, int target) {
        int *result = (int*)malloc((target+1)*sizeof(int));
        for (int x=0; x<target+1; x++)
            result[x]=0;
        result[0]=1;
        //printf("%d",result[0]);
        for (int i=0; i<target+1; i++)
        {
            for (int j=0; j<numsSize; j++)
            {
                if (i>=nums[j])
                {
                    result[i]+=result[i-nums[j]];
                }
            }
        }
        return result[target];
    }
    

  • 0
    J

    @FreeTymeKiyan

    can anyone point out why mine top down approach got TLE

    public int combinationSum411(int[] nums, int target) {
            int[]s=new int[target+1];
            Arrays.sort(nums);
            return f(nums,target,s);
        }
        public int f(int[] nums,int target, int[]s){
            int sum=0;
            if(s[target]>0) return s[target];
            if(target>0){
                for(int i=0;i<nums.length;i++){
                    if(target-nums[i]>0){
                        sum=sum+f(nums,target-nums[i],s);
                    }else if(target-nums[i]==0) {
                        sum=sum+1;
                    }else{
                        break;
                    }
                }
            }
            s[target]=sum;
            return sum;
        }
    
    

  • 0
    L

    Why comb[0]=1?
    for{1,2,3},if target = 0,I think the answer is 0.


  • 0
    F

    @labmem comb[0] is actually target = 1. Note that comb is initialized as new int[target + 1].


  • 0
    O

    I like this answer. This question and this code should be used in classes to teach.

    However...

    Code fails, if you input

    [1,2,3]
    0
    

    The suggested code & the LeetCode suggests "1" as the result, which are both wrong. Figuratively, there is no combination to reach "0" here. The combination of an "empty set" doesn't equal to an integer value of "0".

    Correct way to do it is:

    1. create a main method and a helper method
    2. In main method, check corner cases, sort, check if target is too small
        if (target < nums[0]) return 0;
    
    1. Call helper method to perform dp.
        return helper(nums, target);
    

  • 1
    F

    @ozzyy Thanks for the reply. The problem says that target is positive. I also edited my top-down explanation. Since target is 0 only happens if last round, target == nums[i], we should return 1 as it's the only combination possible.


  • 0
    H

    So what's the run-time of the DP solution? O(n*target)?


  • 0
    H

    @mishrasonu1993
    because top-down just calculate necessary dp.


  • 0
    Z

    @metalsolid1 Very good question, all of the answer below are reasonable. But, I also guess that observation takes place, because top to bottom approach uses stack memory (recursion), which is faster than heap memory. Please, correct me if you don't agree.


  • 0
    B

    Similar idea, but I think using HashMap to finish the DP could be better.


  • 0
    M

    whats the big-O of the recursion solution? is it O(2^n) ?


  • 1
    W

    @hao32 do you know the time complexity?


  • 0
    W

    @mxmingya do you know the time complexity?


Log in to reply
 

Looks like your connection to LeetCode Discuss was lost, please wait while we try to reconnect.