给定一个数组,您必须找到最大可能的两个相等的和

时间:2018-09-09 12:47:24

标签: algorithm

给出一个数组,您必须找到最大可能的两个相等和,可以排除元素。

1,2,3,4,6被赋予数组 我们最多可以有两个等于6 + 2 = 4 + 3 + 1的和

即4,10,18,22, 我们可以得到两个相等的总和,即18 + 4 = 22

除了蛮力查找所有计算并检查两个可能的相等和之外,您将采用什么方法来解决此问题?

edit 1:数组元素的最大数目为N <= 50,每个元素最多可以为1 <= K <= 1000

edit 2:这是我的解决方法https://ideone.com/cAbe4g,在每种情况下,给定的时间限制为5秒,这会花费太多时间。

编辑3:-元素总数之和不能大于1000。

3 个答案:

答案 0 :(得分:14)

推荐的方法

我建议使用DP解决此问题,而不是跟踪A,B(两组的大小),而是跟踪A + B,A-B(两组的总和和差)。

然后针对数组中的每个元素,尝试将其添加到A或B中,或都不添加到两者中。

跟踪总和/差的优点是,您只需跟踪每个差的单个值,即针对该差看到的总和的最大值。

为了提高效率,我建议您从最小到最大顺序进行迭代,并在达到目前为止的最大差异后停止更新DP。

您也只能存储差异的绝对值,而忽略任何大于25000的差异(因为从这一点开始,差异不可能恢复为0)。

Python示例代码

from collections import defaultdict

def max_equal_sum(E):
    D=defaultdict(int)            # Map from abs difference to largest sum
    D[0]=0                        # Start with a sum and difference of 0
    for a in E:                   # Iterate over each element in the array
        D2=D.copy()               # Can keep current sum and diff if element is skipped
        for d,s in D.items():     # d is difference, s is sum
            s2 = s+a              # s2 is new sum
            for d2 in [d-a,d+a]:  # d2 is new difference
                D2[abs(d2)] = max(D2[abs(d2)],s2) # Update with largest sum
        D=D2
    return D[0]/2                 # Answer is half the sum of A+B for a difference of 0

print max_equal_sum([1,2,3,4,6])  # Prints 8
print max_equal_sum([4,10,18,22]) # Prints 22

答案 1 :(得分:2)

值从0到1000的最大集合,其中没有两个等于和的子集,其中包含9个元素,例如:

{1, 2, 4, 8, 16, 32, 64, 128, 256, 512}

如果添加第十个元素,则它将等于前九个值的子集的总和。

如果在排除9个以上元素后发现两个总和相等的子集,则可以将被排除的元素中的两个相等总和相加,以形成更大的相等总和;这意味着您不应排除9个以上的元素。

被排除元素的总和,范围为0到1000。构建一个筛子以检查该范围内的哪些值可以用集合中的元素形成,最多需要50×1000步。 (我们可以存储加到每个总和上的最小数量的值,而不是布尔值,并使用该数量仅包括可以由9个或更少元素组成的总和。)

如果然后查看从小到大的排除数之和,则意味着查看从大到小的排除数之和。对于每个排除值的总和,我们检查哪些元素(最多9个)可以形成该总和(显然这不是一个简单的步骤,但我们知道元素的数量在筛子中存储的最小值与9之间),以及这给了我们一组排除的,因此也提供了一组包含的数字。然后,我们使用类似的筛分技术检查所包含的数字是否可以形成半数和;范围是1到500,最多可以进行50×500步。

在所有这一切中,当然要考虑奇数/偶数:如果总和为奇数,则必须排除具有奇数和的子集;如果总和为偶数,则仅需排除具有偶数和的子集。

我还没有真正弄清楚如何生成最坏情况的输入,因此很难判断最坏情况的复杂度。但我认为一般情况应该可行。


这里有一些活动内容。首先,筛子查找最多9个值的集合的总和,这些集合可以用20个总和为999的值排除(并具有正确的奇/偶性):

function excludedSums(values) {
    var sieve = [0];
    for (var i in values) {
        var temp = [];
        for (var j in sieve) {
            if (sieve[j] == 9) continue;
            var val = values[i] + Number(j);
            if (!sieve[val] || sieve[j] + 1 < sieve[val]) {
                temp[val] = sieve[j] + 1;
            }
        }
        for (var j in temp) {
            sieve[j] = temp[j];
        }
    }
    var odd = values.reduce(function(ac, el) {return ac + el}, 0) % 2;
    for (var i in sieve) {
        if (Number(i) % 2 != odd) delete sieve[i];
    }
    return sieve;
}
var set = [40,7,112,15,96,25,49,49,31,87,39,8,79,40,73,49,63,55,12,70];
var result = excludedSums(set);
for (var i in result) document.write(i + ", ");

接下来,以一定的总和最多包含9个值的集合。在上面的示例中,我们看到了可以排除总和为99的一组或多组;让我们找出这些集合是什么:

function excludedSets(values, target) {
    var sieve = [[[]]];
    for (var i in values) {
        var temp = [];
        for (var j in sieve) {
            var val = values[i] + Number(j);
            if (val > target) continue;
            for (var k in sieve[j]) {
                if (sieve[j][k].length < 9) {
                    if (!temp[val]) temp[val] = [];
                    temp[val].push(sieve[j][k].concat([values[i]]));
                }
            }
        }
        for (var j in temp) {
            if (!sieve[j]) sieve[j] = [];
            for (var k in temp[j]) {
                sieve[j].push(temp[j][k].slice());
            }
        }
    }
    return sieve[target];
}

var set = [40,7,112,15,96,25,49,49,31,87,39,8,79,40,73,49,63,55,12,70];
var result = excludedSets(set, 99);
for (var i in result) document.write(result[i] + "<br>");

(您会在输出中看到一些重复项,因为例如49值在集合中出现了3次。)

现在让我们测试没有排除值的集合是否可以一分为二。我们看到,可以形成例如99的和。值87和12,因此我们从集合中排除了这些值,并得到18个值的总和为900。现在,我们检查是否可以通过将集合中的值相加来形成一半总和450:

function sumPossible(values, target) {
    var sieve = [true];
    for (var i in values) {
        var temp = [];
        for (var j in sieve) {
            var val = values[i] + Number(j);
            if (val < target) temp[val] = true;
            else if (val == target) return true;
        }
        for (var j in temp) sieve[j] = temp[j];
    }
    return false;
}
var set = [40,7,112,15,96,25,49,49,31,39,8,79,40,73,49,63,55,70];
document.write(sumPossible(set, 450));

因此450是该集合的可能一半和。显然,它不是最大的,因为我们随机抽取了总和99作为示例,而不是对所有总和从小到大进行迭代;实际上,第一个选项(不包括值7)将导致最大半数和496。

应该注意的是,集合越大,集合就越有可能被分成两半(如果它具有偶数和,或者如果具有奇数和,则去除了最小的奇数值)。用数百万个随机值集进行的测试,其总数之和最多为1000,这表明对于大于28的任何集大小,都不会出现无法将一半拆分的单个集的情况。(当然可以制作这样的集,例如49个和一个51个。)

答案 2 :(得分:0)

对于数组中的每个元素,存在三种可能性。 (i)将元素纳入第一组 (ii)将元素包含在第二组中 (iii)不包含任何元素 只要第一组和第二组的总和等于,就更新答案。

   public class Main
{
    static int ans = -1;
   public static void find(int[] arr,int sum1,int sum2,int start)
   {    if(sum1==sum2)
         ans = Math.max(ans,sum1);
        if(start==arr.length)
          return;
        find(arr,sum1+arr[start],sum2,start+1);
        find(arr,sum1,sum2+arr[start],start+1);
        find(arr,sum1,sum2,start+1);

   }
   public static void main(String[] args)
   {
       int[] arr = new int[]{1,2,100,101,6,100};
       ans = -1;
       find(arr,0,0,0);
       System.out.println(ans);
   }
}