k Sum

Given n distinct positive integers, integer k (k <= n) and a number target.

Find k numbers where sum is target. Calculate how many solutions there are?

http://www.lintcode.com/en/problem/k-sum/

Discussion

一组数取K个,满足和为某个值,就是Combination Sum问题的变形,只是要求path 的size是k的时候才输出。三十秒给出DFS版本。

Solution 1 - DFS

class Solution {
public:
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    int kSum(vector<int> A, int k, int target) {
        int cnt =  0;
        if(A.empty() || A.size() < k) return 0;
        sort(A.begin(), A.end());
        vector<int> path;
        dfs(A, k, target, path, 0, cnt);
        return cnt;
    }
    void dfs(vector<int> &nums, int k, int gap, vector<int> &path, int pos, int &cnt) {
        if(path.size() == k) {
            if(gap ==0) {
                cnt++;
            }
            return;
        }
        int n = nums.size();
        for(int i=pos; i<n; i++) {
            if(nums[i] > gap) {//prunning
                break;
            }
            path.emplace_back(nums[i]);
            dfs(nums, k, gap-nums[i], path, i+1, cnt);
            path.pop_back();
        }
    }
};

因为只是求个数,没有必要传递path,那怎么记录当前是否到了K个数字呢?k每次减一,到0的时候就可以了啊!

class Solution {
public:
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    int kSum(vector<int> A, int k, int target) {
        int cnt = 0;
        dfs(A, target, k, 0, cnt);
        return cnt;
    }
    void dfs(vector<int> &A, int gap, int k, int start, int &cnt) {
        if(k == 0 ) {
            if(gap == 0) {
                cnt++;
            }
            return;
        }
        for(int i= start; i<=A.size() - k; i++) {
            if(gap - A[i] < 0) {
                break;
            }
            dfs(A, gap - A[i], k-1, i+1, cnt);
        }
    }
};

Follow up

上面求组合的算法时间复杂度是O(C(n, k)),提交的时候不出意外的超时了。怎么剪枝都不行。。。

动归的思路:题目要求前n个数取k个组成和为target有多少种可能,把这句话generalize就是——前i个数取j个和为t的方案有多少?那动归的状态就有了:

  • state: f[i][j][t] 前i个数取j个和为t的方案数
  • function: f[i][j][t] = f[i-1][j][t] + f[i-1][j-1][t-A[i-1]]
    //前i-1个中取j个(不取最新的第i个,也就是A[i-1]),或者前i-1个取j-1个再加上A[i-1]
  • 初始化:前i个中取0个和为0的方案数总是1

Solution 2 DP1

// Time:  O(k * n * t)
// Space: O(k * n * t)
class Solution {
public:
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    int kSum(vector<int> A, int k, int target) {
        if(A.empty() || A.size() < k) return 0;
        int n = A.size();
        vector<vector<vector<int> > > table(n+1, vector<vector<int> >(k+1, vector<int> (target+1, 0)));
        //tavle[i][j][t]: select j numbers from previous i numbers in A, sum to t, how may ways to do this.
        //init: select 0 from previous i numbers, target is t, 1 way only.
        for(int i=0; i<=n; i++) {
            table[i][0][0] = 1;
        }
        //state transformation:
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=k; j++) { //select k numbers at most;
                for(int t=1; t<=target; t++) {
                    table[i][j][t] = table[i-1][j][t]; //DON'T SELECT A[i-1]
                    if(t-A[i-1] <= target && t-A[i-1] >=0)
                        table[i][j][t] += table[i-1][j-1][t-A[i-1]]; //select A[i-1]
                }
            }
        }
        return table[n][k][target];
    }
};

Solution 3 DP2 saving space

状态的变化只跟上一行有关系,自然想到用两行的table就可以了。

// Time:  O(k * n * t)
// Space: O(n * t)
class Solution {
public:
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    int kSum(vector<int> A, int k, int target) {
        if(A.empty() || A.size() < k) return 0;
        int n = A.size();
        vector<vector<vector<int> > > table(2, vector<vector<int> >(k+1, vector<int> (target+1, 0)));
        //tavle[i][j][t]: select j numbers from previous i numbers in A, sum to t, how may ways to do this.
        //init: select 0 from previous i numbers, target is t, 1 way only.
        for(int i=0; i<=1; i++) {
            table[i][0][0] = 1;
        }
        //state transformation:
        for(int i=1; i<=n; i++) {
            for(int j=1; j<=k; j++) { //select k numbers at most;
                for(int t=1; t<=target; t++) {
                    table[i%2][j][t] = table[(i-1)%2][j][t]; //DON'T SELECT A[i-1]
                    if(t-A[i-1] <= target && t-A[i-1] >=0)
                        table[i%2][j][t] += table[(i-1)%2][j-1][t-A[i-1]]; //select A[i-1]
                }
            }
        }
        return table[n%2][k][target];
    }
};

Follow up

j最多取到k,但是当i小于k的时候j最多取到i,把状态转移那里for(int j=1; j<=k; j++)改成for(int j=1; j<=min(i,k); j++)可以节省一半的时间。

Solution 4. DP3: savding space and time

从状态转移方程f[i][j][t] = f[i-1][j][t] + f[i-1][j-1][t-A[i-1]]可以看出来,自底向上要不上面的TOP-DOWN更节省时间,而且用一个二维表就可以了。

class Solution {
public:
    /**
     * @param A: an integer array.
     * @param k: a positive integer (k <= length(A))
     * @param target: a integer
     * @return an integer
     */
    int kSum(vector<int> A, int k, int target) {
        if(A.empty() || A.size() < k) return 0;
        int n = A.size();
        vector<vector<int> >  table(k+1, vector<int> (target+1, 0));
        //tavle[i][j][t]: select j numbers from previous i numbers in A, sum to t, how may ways to do this.
        //init: select 0 from previous i numbers, target is t, 1 way only.
         table[0][0] = 1;
        //state transformation:
        for(int i=1; i<=n; i++) {
            for(int j=min(i,k); j>=1; j--) { //select k numbers at most;
                for(int t=target; t>=A[i-1]; t--) {//注意这里t的下界
                        table[j][t] += table[j-1][t-A[i-1]]; //select A[i-1]
                }
            }
        }
        return table[k][target];
    }
};

results matching ""

    No results matching ""