这是我们的算法期末考试的问题。这是逐字的,因为教授让我们把考试的副本带回家。
- (20分)设I = {r1,r2,...,rn}是一组 n 任意正整数, I 中的值是不同即可。 I 未按任何排序顺序提供。假设我们想找到 I 的子集 I',使得 I'中所有元素的总和正好是100 * ceil(n ^ .5)( I 的每个元素最多只能出现在 I'中一次。提出一个O( n )时间算法来解决这个问题。
醇>
据我所知,它基本上是背包问题的特殊情况,也称为子集和问题......两者都在NP中,理论上在线性时间内无法解决?
所以...这是一个棘手的问题吗?
This SO post基本上解释了如果权重是有界的,可以进行伪多项式(线性)时间近似,但在检查问题中,权重不受限制,并且考虑到检查的整体难度如果教授希望我们知道/想出一个模糊的动态优化算法,我会感到震惊。
答案 0 :(得分:3)
有两件事可以解决这个问题:
基本上,这个问题尖叫动态编程,每个输入都以某种方式对“到达数字”空间的每个部分进行检查。
解决方案的最终目的是确保数字不会自行消失(通过向正确方向扫描),只查看每个数字一次,然后给自己提供足够的信息以重建解决方案。< / p>
以下是一些应该在给定时间内解决问题的C#代码:
int[] FindSubsetToImpliedTarget(int[] inputs) {
var target = 100*(int)Math.Ceiling(Math.Sqrt(inputs.Count));
// build up how-X-was-reached table
var reached = new int?[target+1];
reached[0] = 0; // the empty set reaches 0
foreach (var e in inputs) {
// we go backwards to avoid reaching off of ourselves
for (var i = target; i >= e; i--) {
if (reached[i-e].HasValue) {
reached[i] = e;
}
}
}
// was target even reached?
if (!reached[target].HasValue) return null;
// build result by back-tracking via the logged reached values
var result = new List<int>();
for (var i = target; reached[i] != 0; i -= reached[i].Value) {
result.Add(reached[i].Value);
}
return result.ToArray();
}
我实际上没有测试过上面的代码,所以要小心打字错误,然后再打字。
答案 1 :(得分:1)
以下是O(n)时间的简单解决方案。
由于所需的总和S
大约为O(n^0.5)
,如果我们制定复杂度S^2
的算法,那么我们就是好的,因为我们的算法应具有有效的复杂性{{ 1}}。
在所有元素上迭代一次,检查值是否小于S.如果是,则将其推入新阵列。该数组应包含最多S个元素(O(n ^ .5))
在O(sqrt(n)* logn)时间(&lt; O(n))中按降序对此数组进行排序。这是因为logn&lt; = sqrt(n)表示所有自然数。 https://math.stackexchange.com/questions/65793/how-to-prove-log-n-leq-sqrt-n-over-natural-numbers
现在这个问题是1D背包问题,W = S,元素数= S(上限)。
最大化项目的总重量,看它是否等于S.
可以使用线性时间的动态编程来解决(线性wrt W~S)。
答案 2 :(得分:1)
使用subset-sum problem的典型DP算法将获得O(N)
耗时的算法。我们使用dp[i][k]
(布尔值)来指示前i个项是否具有和k的子集,转换方程为:
dp[i][k] = (dp[i-1][k-v[i] || dp[i-1][k]),
它是O(NM)
,其中N是集合的大小,M是目标总和。由于元素是不同的,并且总和必须等于100*ceil(n^.5)
,我们只需要考虑最初的100 * ceil(n ^ .5)项,然后我们得到N<=100*ceil(n^.5)
和M = 100*ceil(n^.5)
。
DP算法为O(N*M) = O(100*ceil(n^.5) * 100*ceil(n^.5)) = O(n).