Codility上得问题之五 Delta 2011

题目描述:
     给定一个数组N个正整数,给每个数一个符号,正或者负使得所有数的的和的绝对值尽可能大。也就是使得这个
val(A, S) = | sum { A[i]*S[i] for i = 0..N−1 }尽可能大s[i] = +1或者-1。

原题英语说了一大堆,但是实际上把所有的数当做正数的话,其实就是分成两组(一组可能为空),使得差得绝对值尽可能小。这是经典的背包问题……经典的NPC,经典的伪多项式算法。
再看数据范围:
N [0..20000]  每个数范围[-100,+100]。
要求的时间复杂度:  O(N*max(abs(A)) 2 );
要求的空间复杂度: O(N+sum(abs(A)));
 数的个数很多,但数据范围很小,又有那么诡异的复杂度要求……这就是说我们可以类似基数排序那样统计一下每个数出现多少次。然后怎么做背包呢?首先把所有的数当做正数,总和是sum, 我们只要记录能达到的不超过sum/2的最大值就可以了。那么如何打表?我们记录dp[i][j]表示只使用前i种数,达到总和j的时候,最多还能剩余多少个第i种数,如果dp[i][j] = -1说明我们无法用前i种数凑够j。第i种数有have[i]个。(其实i = 0..100)
那么我们有 dp[i][j] = have[i] if dp[i - 1][j] >= 0, 因为我们用前(i - 1)种数已经达到总和j了,第i种数可以一个都不用
                dp[i][j] = max(dp[i][j - i] - 1, - 1);  这是因为我们使用了第i种数,要多使用一个。这里涵盖了-1的情况。
注意更新的顺序由小到大,并且第一维可以省略掉,也就是传说中得滚动数组,不然空间复杂度达不到要求。
代码如下:
int solution(const vector<int> &A) {
    // write your code here...
    int i,j, sum = 0, M = 0;
    for (i = 0; i < A.size(); ++i) {
        int x = (A[i] >= 0)?A[i]:(-A[i]);
        if (x) {
            sum += x;
            if (x > M) {
                M = x;
            }
        }
    }
    if (M == 0) {
        return 0;
    }
    vector<int> have;
    have.resize(M + 1, 0);
    for (i = 0; i < A.size(); ++i) {
        ++have[(A[i] >= 0)?A[i]:(-A[i])];
    }
    int s = (sum >> 1) + 1;
    vector<int> dp;
    dp.resize(s, -1);
    dp[0] = 0;
    int can = 0;
    for (i = 1; i <= M;++i) {
        if (have[i]) {
            for (j = 0; j < s; ++j) {
                if (dp[j] >= 0) {
                    dp[j] = have[i];
                    if (j > can) {
                        can = j;
                    }  
                }
                else if ((j >= i) && (dp[j - i] > 0)) {
                    dp[j] = dp[j - i] - 1;
                    if (j > can) {
                        can = j;
                    }
                }
              
                
            }
        }
    }
 
    return sum - (can << 1);

       
}


简单分析下,
时间复杂度: 统计部分O(N), dp外层循环不超过M次,内层循环是s次,那总复杂度是O(M * s),而s = O(sum) =O(M * N),总复杂度是O(N * M^2)。 其实再细致点分析外层循环也就是O(min(M,N))那么多次,但是没必要了。
空间复杂度:have数组O(M),dp数组O(sum),复杂度O(sum),没搞明白它那个O(N)是啥,感觉上是O(min(M,N))

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值