最小总和大于或等于k的子集

时间:2019-05-08 14:10:39

标签: algorithm dynamic-programming

我正在尝试编写python算法来执行以下操作。 给定一组正整数S,找到总和最小的子集,该子集大于或等于k。

例如: S = [50,103,85,21,30] k = 140

子集= [85、50、21](总和= 146)

初始集中的数字都是整数,k可以任意大。通常,集合中大约有100个数字。

当然,存在遍历所有可能子集的蛮力解决方案,但这在O(2 ^ n)中运行是不可行的。有人告诉我这个问题是NP-Complete,但是应该有一种动态编程方法,使其能够像背包问题一样在伪多项式时间内运行,但是到目前为止,尝试使用DP仍然可以使我找到解决方案就是O(2 ^ n)。

有没有办法解决这个问题?如果是这样,怎么办?我发现DP很难理解,所以我可能错过了一些东西。

非常感谢您的帮助。

1 个答案:

答案 0 :(得分:0)

看到数字不是整数而是实数,我能想到的最好是O(2^(n/2) log (2^(n/2))

乍一看可能看起来更糟,但请注意2^(n/2) == sqrt(2^n)

因此,要实现这种复杂性,我们将在中间使用称为Meet的技术:

  1. 将集合分为大小为n/2n-n/2的2个部分
  2. 使用蛮力生成所有子集(包括空子集)并将其存储在数组中,我们将其称为A和B
  3. 让我们对数组B进行排序
  4. 现在对于A中的每个元素a,如果B[-1] + a >=k,我们可以使用二进制搜索找到B中满足b的最小元素a + b >= k
  5. 我们发现所有这样的a + b对中
  6. 选择最小的

OP现在稍微改变了一个问题,它的整数是动态解决方案:

经典背包没什么多说的

对于[1,n]中的每个i,我们有2个用于设置项目i的选项:  1.包括在子集中的状态从(i, w)(i+1, w + S[i])的变化  2.跳过它,状态从(i, w)变为(i+1, w)

每次我们到达> = k的w时,我们都会更新答案

伪代码:

visited = Set() //some set/hashtable object to store visited states
S = [...]//set of integers from input
int ats = -1;

 void solve(int i, int w) //theres atmost n*k different states so complexity is O(n*k)
{
    if(w >= k)
    {
        if(ats==-1)ats=w;
        else ats=min(ats,w);
        return;
    }
    if(i>n)return;

    if(visited.count(i,w))return; //we already visited this state, can skip
    visited.insert(i,w)=1;

    solve(i+1, w + S[i]); //take item
    solve(i+1, w); //skip item
}

solve(1,0);
print(ats);