我尝试过这个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);
}
答案 0 :(得分:2)
我无法解决。但这是官方的答案:https://codility.com/media/train/solution-min-abs-sum.pdf
下面是从上面的链接复制的(稍作修改):
请注意,数字范围很小(最多100个)。因此,必须有很多 重复的数字。令count [i]表示值i出现的次数。我们可以 一次处理所有出现相同值的事件。第一 我们计算值计数[i] 然后我们创建数组dp,使得:
最初,所有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;
}