Codility MinAbsSum

时间:2017-07-04 04:57:52

标签: c++ algorithm

我尝试过这个Codility测试:MinAbsSum。 https://codility.com/programmers/lessons/17-dynamic_programming/min_abs_sum/

我通过搜索整个树的可能性来解决问题。结果没问题,但是,由于大输入超时,我的解决方案失败了。换句话说,时间复杂度不如预期。我的解决方案是O(nlogn),与树木一样正常。但是这个编码测试在“动态编程”一节中,必须有一些方法来改进它。我尝试先将整个集合求和然后使用这些信息,但总是在我的解决方案中缺少一些东西。有没有人知道如何使用DP改进我的解决方案?

#include <vector>
using namespace std;

int sum(vector<int>& A, size_t i, int s)
{
   if (i == A.size())     
      return s;

   int tmpl = s + A[i];
   int tmpr = s - A[i];
   return min (abs(sum(A, i+1, tmpl)), abs(sum(A, i+1, tmpr)));
}

int solution(vector<int> &A) {
   return sum(A, 0, 0);   
}

5 个答案:

答案 0 :(得分:2)

我无法解决。但这是官方的答案:https://codility.com/media/train/solution-min-abs-sum.pdf

下面是从上面的链接复制的(稍作修改):

请注意,数字范围很小(最多100个)。因此,必须有很多 重复的数字。令count [i]表示值i出现的次数。我们可以 一次处理所有出现相同值的事件。第一 我们计算值计数[i] 然后我们创建数组dp,使得:

  • dp [j] = -1,如果我们无法获得和j,
  • dp [j]> = ­ 0如果我们可以得到和j。

最初,所有j的dp [j] = -1(dp [0] = 0除外)。然后我们扫描出现的所有值 a 在一个;我们将所有 a 都考虑为count [ a ]> 0。 对于每个这样的 a ,我们更新dp,即dp [j]表示(最大)剩余多少个 a 值。 求和后请注意,如果先前的值dp [j]> = 0,那么我们可以设置dp [j] = count [ a ] 因为不需要值 a 即可获得总和j。否则,我们必须先求和j-a, 然后使用 a 数字 a 得出总和j。在这种情况下,dp [j] = dp [j- a ]-1。 使用此算法,我们可以标记所有总和值,然后选择最佳值(最接近S的一半,A的绝对值之和)。

def MinAbsSum(A):
    N = len(A)
    M = 0
    for i in range(N):
        A[i] = abs(A[i])
        M = max(A[i], M)
    S = sum(A)
    count = [0] * (M + 1)
    for i in range(N):
        count[A[i]] += 1
    dp = [-1] * (S + 1)
    dp[0] = 0
    for a in range(1, M + 1):
        if count[a] > 0:
            for j in range(S):
                if dp[j] >= 0:
                    dp[j] = count[a]
                elif (j >= a and dp[j - a] > 0):
                    dp[j] = dp[j - a] - 1
    result = S
    for i in range(S // 2 + 1):
        if dp[i] >= 0:
            result = min(result, S - 2 * i)
    return result

顺便说一句,fladam提供的Java答案尽管输入[2、3、2、2、3]得分为100%,但返回的结果却错误。

Java解决方案

import java.util.Arrays;

public class MinAbsSum{
    static int[] dp;

    public static void main(String args[]) {
        int[] array = {1, 5,  2, -2};

        System.out.println(findMinAbsSum(array));
    }

    public static int findMinAbsSum(int[] A) {
        int arrayLength = A.length;
        int M = 0;
        for (int i = 0; i < arrayLength; i++) {
            A[i] = Math.abs(A[i]);
            M = Math.max(A[i], M);
        }

        int S = sum(A);
        dp = new int[S + 1];
        int[] count = new int[M + 1];
        for (int i = 0; i < arrayLength; i++) {
            count[A[i]] += 1;
        }
        Arrays.fill(dp, -1);
        dp[0] = 0;
        for (int i = 1; i < M + 1; i++) {
            if (count[i] > 0) {
                for(int j = 0; j < S; j++) {
                    if (dp[j] >= 0) {
                        dp[j] = count[i];
                    } else if (j >= i && dp[j - i] > 0) {
                        dp[j] = dp[j - i] - 1;
                    }
                }
            }
        }
        int result = S;
        for (int i = 0; i < Math.floor(S / 2) + 1; i++) {
            if (dp[i] >= 0) {
                result = Math.min(result, S - 2 * i);
            }
        }
        return result;
    }

    public static int sum(int[] array) {
        int sum = 0;
        for(int i : array) {
            sum += i;
        }

        return sum;
    }
}

答案 1 :(得分:1)

这个解决方案(在Java中)得分均为100%(正确性和性能)

public int solution(int[] a){
    if (a.length == 0) return 0;
    if (a.length == 1) return a[0];
    int sum = 0;
    for (int i=0;i<a.length;i++){
        sum += Math.abs(a[i]);
    }
    int[] indices = new int[a.length];
    indices[0] = 0;
    int half = sum/2;
    int localSum = Math.abs(a[0]);
    int minLocalSum = Integer.MAX_VALUE;
    int placeIndex = 1;
    for (int i=1;i<a.length;i++){
        if (localSum<half){
            if (Math.abs(2*minLocalSum-sum) > Math.abs(2*localSum - sum))
                minLocalSum = localSum;
            localSum += Math.abs(a[i]);
            indices[placeIndex++] = i;
        }else{
            if (localSum == half)
                return Math.abs(2*half - sum);

            if (Math.abs(2*minLocalSum-sum) > Math.abs(2*localSum - sum))
                minLocalSum = localSum;
            if (placeIndex > 1) {
                localSum -= Math.abs(a[indices[placeIndex--]]);
                i = indices[placeIndex];
            }
        }
    }
    return (Math.abs(2*minLocalSum - sum));

}

此解决方案将所有元素视为正数,并且它希望尽可能接近所有元素的总和 除以2 (在这种情况下,我们知道所有其他元素的总和将是远离一半的相同delta - &gt; abs sum将是最小可能的)。 它是通过从第一个元素开始并相继将其他元素添加到&#34; local&#34; sum(并且记录总和中元素的索引)直到它达到x> = sumAll / 2的总和。如果x等于sumAll / 2,我们有一个最优解。如果没有,我们将退回到indices数组中并继续选择该位置中最后一次迭代结束的其他元素。结果将是&#34; local&#34;具有最接近0的abs((sumAll - sum) - sum)的和;

答案 2 :(得分:0)

你几乎是实际解决方案的90%。看来你很了解递归。现在,您应该在此处使用您的程序进行动态编程。

 动态编程只是对递归的记忆,因此我们不会一次又一次地计算相同的子问题。如果遇到相同的子问题,我们返回先前计算和记忆的值。记忆可以在2D数组的帮助下完成,例如dp [] [],其中第一个状态表示数组的当前索引,第二个状态表示求和。

针对此问题,您有时可以贪婪地决定跳过一个电话,而不是从每个州调用这两个州。

答案 3 :(得分:0)

我发明了另一种解决方案,比前一种更好。我不再使用递归了。 这个解决方案工作正常(所有逻辑测试都通过),并且还通过了一些性能测试,但不是全部。我怎么能改进它?

#include <vector>
#include <set> 
using namespace std;

int solution(vector<int> &A) {
    if (A.size() == 0) return 0;

    set<int> sums, tmpSums;        
    sums.insert(abs(A[0]));
    for (auto it = begin(A) + 1; it != end(A); ++it)
    {        
        for (auto s : sums)
        {
            tmpSums.insert(abs(s + abs(*it)));
            tmpSums.insert(abs(s - abs(*it)));            
        }
        sums = tmpSums;
        tmpSums.clear();
    }

    return *sums.begin();
}

答案 4 :(得分:0)

以下是C ++中的官方答案(在任务,正确性和性能方面得分100%):

#include <cmath>
#include <algorithm>
#include <numeric>
using namespace std;

int solution(vector<int> &A) {
    // write your code in C++14 (g++ 6.2.0)
    const int N = A.size();
    int M = 0;
    for (int i=0; i<N; i++) {
        A[i] = abs(A[i]);
        M = max(M, A[i]);
    }
    int S = accumulate(A.begin(), A.end(), 0);
    vector<int> counts(M+1, 0);
    for (int i=0; i<N; i++) {
        counts[A[i]]++;
    }
    vector<int> dp(S+1, -1);
    dp[0] = 0;
    for (int a=1; a<M+1; a++) {
        if (counts[a] > 0) {
            for (int j=0; j<S; j++) {
                if (dp[j] >= 0) {
                    dp[j] = counts[a];
                } else if ((j >= a) && (dp[j-a] > 0)) {
                    dp[j] = dp[j-a]-1;
                }
            }
        }
    }
    int result = S;
    for (int i =0; i<(S/2+1); i++) {
        if (dp[i] >= 0) {
            result = min(result, S-2*i);
        }
    }
    return result;
}