【问题标题】:Given an array, you have to find the max possible two equal sum给定一个数组,你必须找到最大可能的两个相等的和
【发布时间】:2019-02-14 02:44:15
【问题描述】:

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

1,2,3,4,6 是给定数组 我们最多可以有两个相等的和为 6+2 = 4+3+1

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

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

编辑1:数组元素的最大数量为N

编辑 2:这是我的解决方案 https://ideone.com/cAbe4g,如果每个案例的给定时间限制为 5 秒,这会花费太多时间。

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

【问题讨论】:

  • 只要不重复,等式两边可以有多少个数字是没有限制的?
  • 是的,号码一旦在一侧使用,就无法在另一侧添加。
  • 这是the subset sum problem的更难版本。
  • @vivek_23 问题是有很多不同的方法可以组合数组中的元素,计算起来并不简单,但在我看来,它的增长速度与阶乘一样快。这就是为什么即使使用非常小的阵列,蛮力解决方案也可能失败。这是一个棘手的问题!
  • @Atiq 问题出在哪里?我们确定那里有一个多项式答案吗?如果没有,我宁愿不要投入太多时间^^

标签: algorithm


【解决方案1】:

推荐方法

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

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

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

为了提高效率,我建议您按照从小到大的顺序遍历元素,并在达到迄今为止看到的最大差异时停止更新 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

【讨论】:

  • 嗯听起来很有趣,但不确定我是否理解你的建议。可以用伪代码写吗?
  • 好,去看看
  • 不错的解决方案。不过副本听起来很贵。 D 能变多大?真的有必要吗?
  • @Dici D 最多可以有 N.K 个值,需要复制 N 次。对于这个问题的参数,这会产生 50 个长度为 50k 的字典的副本。我估计最坏的情况是:max_equal_sum(range(1000-50,1000)) 在 Python 中需要 0.5 秒。
  • 这个问题正在讨论这个答案:stackoverflow.com/questions/52270073/…
【解决方案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 个值的集合的总和,这些值可以被排除(并且具有正确的奇数/偶数)用总和为 999 的 20 个值进行测试:

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。

需要注意的是,集合越大,越有可能将集合分成两半(如果它的和是偶数,或者如果它的和是奇数,则删除最小的奇数值)。对数百万组随机值进行的测试显示,对于任何大于 28 的集合大小,没有一个集合不能分成两半。(当然可以制作这样的集合,例如49 个和一个 51 个。)

【讨论】:

  • 我认为这会起作用,我可能会尝试稍后对其进行编码,但显然彼得的解决方案更优越:-)
【解决方案3】:

对于数组中的每个元素,存在三种可能性。 (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);
   }
}

【讨论】:

  • 请描述你的方法。
【解决方案4】:

#include

使用命名空间标准;

/* 蛮力递归求解。 */

void solve(vector&arr, int &ans, int p1, int p2, int idx, int mx_p){

// if p1 == p2, we have a potential answer
if(p1 == p2){
    ans = max(ans, p1);
}

//base case 1:
if((p1>mx_p) || (p2>mx_p) || (idx >= arr.size())){
    return;
}

// leave the current element
solve(arr, ans, p1, p2, idx+1, mx_p);

// add the current element to p1
solve(arr, ans, p1+arr[idx], p2, idx+1, mx_p);

// add the current element to p2
solve(arr, ans, p1, p2+arr[idx], idx+1, mx_p);

}

/* 带记忆的递归求解。 */

int solve(vector>&memo, vector&arr, int p1, int p2, int idx, int mx_p){

//base case 1:
if((p1>mx_p) || (p2>mx_p) || (idx>arr.size())){
    return -1;
}

// memo'ed answer
if(memo[p1][p2][idx]>-1){
    return memo[p1][p2][idx];
}


// if p1 == p2, we have a potential answer
if(p1 == p2){
    memo[p1][p2][idx] = max(memo[p1][p2][idx], p1);
}

// leave the current element
memo[p1][p2][idx] = max(memo[p1][p2][idx], solve(memo, arr, p1, p2, 
                        idx+1, mx_p));

// add the current element to p1
memo[p1][p2][idx] = max(memo[p1][p2][idx], 
                        solve(memo, arr, p1+arr[idx], p2, idx+1, mx_p));

// add the current element to p2
memo[p1][p2][idx] = max(memo[p1][p2][idx], 
                        solve(memo, arr, p1, p2+arr[idx], idx+1, mx_p));
                        
return memo[p1][p2][idx];

}

int main(){

vector<int>arr = {1, 2, 3, 4, 7};
int ans = 0;
int mx_p = 0;

for(auto i:arr){
    mx_p += i;
}
mx_p /= 2;

vector<vector<vector<int>>>memo(mx_p+1, vector<vector<int>>(mx_p+1, 
                                     vector<int>(arr.size()+1,-1)));
ans = solve(memo, arr, 0, 0, 0, mx_p);
ans = (ans>=0)?ans:0;

// solve(arr, ans, 0, 0, 0, mx_p);
cout << ans << endl;
return 0;

}

【讨论】:

  • 请用正确的代码格式和更多的解释文本来改进你的好答案。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-10-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-01-21
相关资源
最近更新 更多