【问题标题】:Subset with smallest sum greater or equal to k最小和大于或等于 k ​​的子集
【发布时间】:2019-09-26 07:22:35
【问题描述】:

我正在尝试编写一个 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 应用于这个问题?如果是这样,怎么做?我发现 DP 很难理解,所以我可能遗漏了一些东西。

非常感谢任何帮助。

【问题讨论】:

  • 伪多项式时间方法可以考虑所有可能的值 [0.1, 0.2, ...14.6]。现在从 0.1 开始,构建您可能的解决方案,直到 14.6。要为 14.6 构建解决方案,您可以将结果用于 (0.1, 14.5) 和 (0.2, 14.4) 等等。
  • 对数字进行排序。做 DFS 添加最高未使用值并在超过 k 时停止,然后回溯以找到下一个解决方案。当剩余数不能加到 k 时停止。只有 100 深的堆栈,递归解决方案可能工作正常。

标签: algorithm dynamic-programming


【解决方案1】:

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

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

因此,为了实现这种复杂性,我们将使用称为中间相遇的技术:

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

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);

【讨论】:

  • 对不起,nubers 实际上都是整数。另外,请注意,解决方案可能包含 2 个以上的数字。而且这样的解决方案可能比由两个数字组成的解决方案更好。
  • a 和 b 这里代表任意大小的子集,因此所有子集都包括在内,但现在您更新了 n
  • 我在将其实现为 python 代码时遇到了麻烦。返回的回报是什么?
  • @Juan_Ctan02 它什么都不返回,只是阻止了其余的函数代码运行。 Python 需要一个值才能返回,因此您可以使用 return None 但任何值都可以使用
  • 概率方法可以吗?例如。您可以在这里使用模拟退火,方法是随机添加数组的 elts,直到您第一次越过阈值,然后交换成对的元素以进行改进,直到您不能。这不太可能为您带来最好的结果,但应该会给您带来非常好的结果。
猜你喜欢
  • 2021-06-04
  • 1970-01-01
  • 2017-03-16
  • 1970-01-01
  • 1970-01-01
  • 2013-08-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多