【问题标题】:minimum difference between sum of two subsets两个子集之和之间的最小差
【发布时间】:2011-05-11 20:31:03
【问题描述】:

伙计们,

遇到了一个问题...觉得这很有趣...正在稍微修改一下,请加油。

给定一组整数(范围 0-500),找出两个子集之和之间的最小差异,该差异可以通过将它们几乎均分来形成。 (比如说整数的个数是 n,如果 n 是偶数,每个集合必须有 n/2 个元素,如果 n 是奇数,一个集合有 (n-1)/2 个元素,另一个有 (n+1)/2 个元素)

样本输入:1 2 3 4 5 6

最小差异 = 1(子集为 1 4 6 和 2 3 5 )

样本输入 2:[1 1 1 1 2 2 2 2]

最小差异 = 0(子集为 1 1 2 2 和 1 1 2 2)

这个问题有 DP 方法吗。

谢谢大家...

拉吉...

【问题讨论】:

  • 强调:OP 对构建子集不感兴趣,只对获得最小值感兴趣。当然,是否可以在不构建子集的情况下这样做是一个悬而未决的问题。

标签: algorithm dynamic-programming subset


【解决方案1】:

这个问题看起来几乎像“平衡分区”。

您可以使用 DP 方法构建求解平衡分区的伪多项式时间算法。见问题 7http://people.csail.mit.edu/bdean/6.046/dp/

听起来您可以采用类似的方法。

【讨论】:

  • adl,是的,通过将其分成两组,我们将知道平衡...有没有办法在不实际构造子集的情况下仅找到最小差异...
  • @Rajan:是的,有一种启发式方法,使用最小堆将数字的差异存储在一个循环中,直到你只剩下一个数字。这样,您可以获得尽可能小的差异。请参阅en.wikipedia.org/wiki/… 了解更多信息。
【解决方案2】:

我最近使用 c++ 中的动态编程解决了这个问题。我没有修改代码来回答你的问题。但是更改一些常量和少量代码应该可以。

下面的代码读取并解决了 N 个问题。每个问题都有一些人(在您的情况下为整数)和他们的权重(整数值)。此代码尝试将集合分成 2 组,差异最小。

#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAX_PEOPLE 100
#define MAX_WEIGHT 450
#define MAX_WEIGHT_SUM MAX_PEOPLE*MAX_WEIGHT
using namespace std;

int weights[MAX_PEOPLE];
//bool table[MAX_PEOPLE + 1][MAX_WEIGHT_SUM + 1]; 

bool** create2D(int x, int y) {
    bool **array = new bool*[x];
    for (int i = 0; i < x; ++i) {
        array[i] = new bool[y];
        memset(array[i], 0, sizeof(bool)*y);
    }
    return array;
}

void delete2D(int x, int y, bool **array) {
    for (int i = 0; i < x; ++i) {
        delete[] array[i];
    }
    delete[] array;
}

void memset2D(int x, int y, bool **array) {
    for(int i = 0; i < x; ++i)
        memset(array[i], 0, sizeof(bool)*y);
}

int main(void) {
    int n, N, W, maxDiff, teamWeight, temp;
    int minWeight = MAX_WEIGHT, maxWeight = -1;
    cin >> N;
    while(N--) {
        cin >> n;
        W = 0;
        for(int i = 0; i < n; ++i) {
            cin >> weights[i];
            if(weights[i] < minWeight)
                minWeight = weights[i];
            if(weights[i] > maxWeight)
                maxWeight = weights[i];

            W += weights[i];
        }
        int maxW = maxWeight + (W>>1);
        int maxn = n>>1;
        int index = 0;
    /* 
       table[j][i] = 1 if a team of j people can form i weight 
                        from K people, where k is implicit in loop
       table[j][i] = table[j-1][i-weight[j]] if i-weight[j] >=0
     */
        bool **table = create2D(maxn+1, maxW+1);
        //memset2D(maxn+1, maxW+1, table);
        //memset(table, 0, sizeof(table));
        table[0][0] = true;
        /* for k people what can be formed?*/
        for(int k = 0; k < n; ++k) {
            /* forming team of size j out of k people*/
            for(int j = min(k, maxn) ; j >= 1; --j) { 
                /* using j people out of k, can I make weight i?*/
                for(int i = maxW; i >=minWeight ; --i) {
                    if (table[j][i] == false) {
                        /*do not consider k if more than allowable*/
                        index = i - weights[k];
                        if (index < 0) break;
                        /*if without adding k, we can make the weight
                          limit with less than one person then one can
                          also make weight limit by adding k.*/
                        table[j][i] = table[j-1][index];
                    } /*outer if ends here*/
                } /* ith loop */
            } /* jth loop */
        } /* kth loop */

        maxDiff = MAX_WEIGHT_SUM ;
        teamWeight = 0;
        for(int i = 0; i <= maxW; ++i) {
            if (table[n/2][i]) {
                temp = abs(abs(W - i) - i);
                if (temp < maxDiff) {
                    maxDiff = temp;
                    teamWeight = i;
                }
            }
        }
        delete2D(n+1, maxW+1, table);
        teamWeight = min(teamWeight, W-teamWeight);
            cout << teamWeight << " " << W - teamWeight << endl;
        if(N)
            cout << endl;
    }
        return 0;
}

【讨论】:

    【解决方案3】:

    思考这个问题的一个好方法是,如果你有这个问题的 DP 解决方案,你能用它在 P 时间内回答子集总和吗?如果是这样,那么您的 DP 解决方案可能不正确。

    【讨论】:

    • hmmm @Kevin bt 我们还需要你考虑将它分成相等的部分,仪式......所以它也可以分割...... bt 我们只对差异感兴趣......
    • 如果整数是有界的(如这里的 0-500 范围),您可以在多项式时间内求解子集和。 en.wikipedia.org/wiki/…
    【解决方案4】:

    这似乎是Partition problem 的一个实例,它是NP-Complete。

    根据维基百科的文章,有一个伪多项式时间动态规划解决方案。

    【讨论】:

      【解决方案5】:

      我用 C++ 编写了这个程序,假设最大和可以是 10000。

      #include <iostream>
      #include <vector>
      #include <memory>
      #include <cmath>
      
      using namespace std;
      typedef vector<int> VecInt;
      typedef vector<int>::size_type VecSize;
      typedef vector<int>::iterator VecIter;
      
      class BalancedPartition {
      public:
          bool doBalancePartition(const vector<int>*const & inList, int sum) {
              int localSum = 0,  j;
              bool ret = false;
              int diff = INT_MAX, ans=0;
      
              for(VecSize i=0; i<inList->size(); ++i) {
                  cout<<(*inList)[i]<<"\t";
              }
              cout<<endl;
              for(VecSize i=0; i<inList->size(); ++i) {
                  localSum += (*inList)[i];
              }
              M.reset(new vector<int>(localSum+1, 0));
              (*M)[0] = 1;
              cout<<"local sum "<<localSum<<" size of M "<<M->size()<<endl;
      
              for(VecSize k=0; k<inList->size(); ++k) {
                  for(j=localSum; j>=(*inList)[k]; --j) {
                      (*M)[j] = (*M)[j]|(*M)[j-(*inList)[k]];
                      if((*M)[j]) {
                          if(diff > abs(localSum/2 -j)) {
                              diff = abs(localSum/2 -j);
                              ans = j;
                          }
                      }
                  }
              }
              mMinDiffSubSumPossible = abs(localSum - 2*ans);
              return ret;
          }
      
          friend ostream& operator<<(ostream& out, const BalancedPartition& bp) {
              out<<"Min diff "<<bp.mMinDiffSubSumPossible;
              return out;
          }
          BalancedPartition(): mIsSumPossible(false), mMinDiffSubSumPossible(INT_MAX) {
      
          }
      private:
          shared_ptr<vector<int> > M;
          bool mIsSumPossible;
          int mMinDiffSubSumPossible;
          static const int INT_MAX = 10000;
      };
      
      int main(void) {
          shared_ptr<BalancedPartition> bp(new BalancedPartition());
          int arr[] = {4, 12, 13, 24, 35, 45};
          vector<int> inList(arr, arr + sizeof(arr) / sizeof(arr[0]));
          bp->doBalancePartition(&inList, 0);
          cout<<*bp;
          return 0;
      }
      

      【讨论】:

        【解决方案6】:
            from random import uniform
            l=[int(uniform(0, 500)) for x in xrange(15)]
            l.sort()
            a=[]
            b=[]
            a.append(l.pop())
            while l:
                if len(a) > len(b):
                    b.append(l.pop())
                elif len(b) > len(a):
                    a.append(l.pop())
                elif sum(a) > sum(b):
                    b.append(l.pop())
                else:
                    a.append(l.pop())
        
            print a, b
            print sum(a), sum(b)
            print len(a), len(b)
        

        下一步我会尝试从相反的列表中找到一对数字,其差为总和差的一半(或接近),然后交换它们。

        【讨论】:

        • l 可以有任意数量的元素,其中每个元素都在 (1, 500)` 范围内。如果 l 是 (1,500) 范围,则差值是 0 或 1。
        • 感谢您的意见。解决了这个问题。
        • 对于 [13, 59, 115, 175, 180, 214, 251, 317, 372, 416] 这给出 2,但正确答案是 0: sum([13, 59, 251, 317, 416]) == sum([115, 175, 180, 214, 372]) == 1056.
        • 是的,它没有达到完美的效果。
        猜你喜欢
        • 2011-09-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2019-10-06
        相关资源
        最近更新 更多