需要解决这个算法难题的想法
Posted
技术标签:
【中文标题】需要解决这个算法难题的想法【英文标题】:Need idea for solving this algorithm puzzle 【发布时间】:2012-01-25 09:38:57 【问题描述】:过去我遇到过一些与此类似的问题,但我仍然不知道如何解决这个问题。问题是这样的:
给定一个大小为 n
Input: Output:
5 3 >> n = 5 k = 3 3
2 1 1 2 3
将数组拆分为 2+1 | 1+2 | 3 将最小化 m。
我的蛮力想法是让第一个子数组在位置 i 结束(对于所有可能的 i),然后尝试以最好的方式将数组的其余部分拆分为 k-1 个子数组。但是,这是指数解决方案,永远不会奏效。
所以我正在寻找解决问题的好主意。如果你有请告诉我。
感谢您的帮助。
【问题讨论】:
回溯会带你到那里。 这是背包问题的变体吗? en.wikipedia.org/wiki/Knapsack_problem @BlueMonkMN 我认为这个问题更容易。在我不使用动态编程的地方查看我的答案。但是,我不完全确定它是否有效或更快。 这是二分搜索。如果子数组不必是连续的,那就很难了。 【参考方案1】:你可以使用动态规划来解决这个问题,但实际上你可以在答案上使用贪婪和二分搜索来解决。该算法的复杂度为O(n log d)
,其中d
是输出答案。 (上限将是数组中所有元素的总和。)(或输出位大小为O( n d )
)
这个想法是对你的m
进行二进制搜索 - 然后在数组上贪婪地前进,将当前元素添加到分区中,除非添加当前元素将其推到当前 m
之上 - 在这种情况下你开始一个新的分区。如果使用的分区数小于或等于您给定的输入k
,则当前的m
是成功的(并因此调整您的上限)。否则,您使用了太多分区,并提高了m
的下限。
一些伪代码:
// binary search
binary_search ( array, N, k )
lower = max( array ), upper = sum( array )
while lower < upper
mid = ( lower + upper ) / 2
// if the greedy is good
if partitions( array, mid ) <= k
upper = mid
else
lower = mid
partitions( array, m )
count = 0
running_sum = 0
for x in array
if running_sum + x > m
running_sum = 0
count++
running_sum += x
if running_sum > 0
count++
return count
这在概念上应该更容易提出。另请注意,由于 partitions 函数的单调性,您实际上可以跳过二进制搜索并进行线性搜索,如果您确定输出 d
不会太大:
for i = 0 to infinity
if partitions( array, i ) <= k
return i
【讨论】:
哇,这是一个很容易实现但很难想出的解决方案(至少对我来说)。感谢您的帮助。 老实说,对于某些问题,我发现自己对动态规划更有信心,因为(对我来说)证明最优性比证明贪婪是正确的更容易。 不错的拉里,这太棒了【参考方案2】:动态编程。做一个数组
int best[k+1][n+1];
其中best[i][j]
是您可以实现拆分数组int i
子数组的第一个j
元素的最佳方法。 best[1][j]
只是第一个 j
数组元素的总和。有了行i
,你计算行i+1
如下:
for(j = i+1; j <= n; ++j)
temp = min(best[i][i], arraysum[i+1 .. j]);
for(h = i+1; h < j; ++h)
if (min(best[i][h], arraysum[h+1 .. j]) < temp)
temp = min(best[i][h], arraysum[h+1 .. j]);
best[i+1][j] = temp;
best[m][n]
将包含解决方案。算法是 O(n^2*k),可能有更好的可能。
编辑:结合了 ChingPing、toto2、Coffee on Mars 和 rds 的想法(按照我目前在本页看到的顺序)。
设置A = ceiling(sum/k)
。这是最小值的下限。要找到最小值的良好上限,请通过上述任何方法创建一个良好的分区,移动边界,直到找不到任何仍然会减小最大子和的简单移动。这给了你一个上限 B,不比下限大很多(如果它更大,你会发现通过移动边框很容易改进,我认为)。
现在继续使用 ChingPing 的算法,已知的上限减少了可能的分支数量。最后一个阶段是O((B-A)*n),发现B
未知,但我猜比O(n^2)好。
【讨论】:
我认为这会起作用 :D 只是一个建议,因为每个元素的值都有一个限制为 100...我们可以为 j = 预先计算 arraysum[0...j] 的值0 到 n.. 然后 arraysum[i...j]==arraysum[0...j]-arraysum[0...i].. 将其降低到 O(n*k) 是的,我还将累积和存储在一个数组中,所以arraysum[a .. b]
将变为cum[b] - cum[a-1]
,但这不会使它成为 O(n*k),@987654335 中的二次方@ 行为源于我们必须检查 j-i
最后一个子数组的可能位置以找到 best[i+1][j]
。当然,人们可以通过走捷径来减少一些常数因素。
err..对不起你是对的..只是另一件事..你认为它应该是最好的[i+1][j] = min( best[i+1][j] ,温度)
不,我认为不应该,best[i+1][j]
在找到temp
中的最小值后仅设置一次(因此,实际上,我们可以消除temp
,这可能更清楚)。
@ChingPing 啊,我脑子里走错线了,完全被误解了。当然你是对的,我们需要min
。【参考方案3】:
我有一个糟糕的分支定界算法(请不要对我投反对票)
首先取数组和除以 k 的总和,这为您提供了最佳案例界限,即平均 A。此外,我们将为任何分支 GO(全局最优)保留迄今为止看到的最佳解决方案。让我们考虑我们在某个数组元素之后放置一个分隔符(逻辑)作为分区单元,我们必须放置 k-1 个分区。现在我们将这样贪婪地放置分区,
遍历数组元素对它们求和,直到你看到在下一个位置我们将超过 A,现在创建两个分支,一个将分隔符放在这个位置,另一个放在下一个位置,递归执行此操作并设置GO = min (GO, answer for a branch )。 如果在任何分支中的任何一点,我们有一个大于 GO 的分区或位置数小于我们绑定的分区。最后你应该有 GO 作为你的答案。
编辑: 正如 Daniel 所建议的,我们可以稍微修改分隔线放置策略以放置它,直到您达到 A 的元素总和或剩余的位置少于分隔线。
【讨论】:
我认为这通常会做得很好。增强功能是重新计算您要穿过它的每个点的可能最优值。我们从A = ceiling(sum/k)
开始。在running_sum[i]
将超过A
的第一个点,计算B = ceiling((sum-running_sum[i-1])/(k-1))
。如果B >= running_sum[i]
,则不需要分支,可以更新A = running_sum[i]
。类似地,对于稍后的交叉点,如果剩余的平均值大于通过交叉得到的平均值,则不需要分支。
感谢大家的慷慨投票,但我的解决方案在所有情况下都不是很好......考虑 1 1 1 9 作为数组和 3 个分区,我的解决方案永远不会得到答案..我不介意撤消投票.. :)
好的,在这样的极端情况下工作,A = max max array, ceiling(sum/k)
,不要跑得太远以至于你的元素比剩余的子数组少。那我还是觉得挺好的。【参考方案4】:
这只是一个想法的草图......我不确定它是否有效,但它非常简单(而且可能也很快)。
你开始说,让分隔均匀分布(实际上你如何开始并不重要)。
求每个子数组的总和。 找到总和最大的子数组。 查看左右相邻子数组,如果左侧子数组的总和小于右侧子数组,则将左侧的间距移动一格(反之亦然)。 重做当前最大和的子数组。
你会遇到一些情况,你会在相同的两个位置之间不断反弹,这可能意味着你有解决方案。
编辑:见@rds 的评论。您必须更加努力地考虑弹跳解决方案和最终条件。
【讨论】:
这是不正确的。反例:[1; 40; 50; 1个; 2; 40]其中k = 3。从 (1;40)(50;1)(2;40) 开始。中间最大。左边较低。转到 (1;40;90)(1)(2;40)。回到 (1;40)(50;1)(2;40)。结束。但是有 (1;40)(50)(1;2;40)。 @rds 谢谢。这就是为什么我有“将可能意味着你有解决方案”。我将添加一个编辑。【参考方案5】:我的想法,不幸的是它不起作用:
-
将数组拆分为 N 个子数组
找出总和最小的两个连续子数组
合并第 2 步中找到的子数组,形成一个新的连续子数组
如果子数组的总数大于 k,则从第 2 步开始迭代,否则结束。
【讨论】:
不幸的是,这并不总是有效。例如,这将产生2 | 1 1 | 2 3
作为第一步,并以2 1 1 | 2 | 3
或2 | 1 1 2 | 3
结束。然后,您需要检查是否可以通过移动边界来减小最大值。它可能会为您提供移动边界的良好起始位置,并且在大多数情况下,您会很快找到最佳位置,但我不确定是否存在您会陷入局部但不是全局最小值的情况.
是的,两个小值接近的问题似乎总是会导致局部最小值,因为算法会将它们合并在一起,而不是合并到更接近的值。【参考方案6】:
如果你的数组有随机数,你可以希望每个子数组都有 n/k 的分区是一个很好的起点。
从那里
-
通过计算总和来评估此候选解决方案
存储此候选解决方案。例如:
每个子数组的索引数组
子数组求和的对应最大值
减小最大子数组的大小:创建两个新的候选者:一个子数组从 index+1 开始;一个以 index-1 结尾的子数组
评估新候选人。
如果它们的最大值更高,则丢弃
如果它们的最大值较低,则在 2 上迭代,除非该候选者已被评估,在这种情况下它就是解决方案。
【讨论】:
以上是关于需要解决这个算法难题的想法的主要内容,如果未能解决你的问题,请参考以下文章
有没有办法测量这个算法解决这个 Soduku 拼图所花费的时间?
强化学习发现矩阵乘法算法!DeepMind登Nature封面,用 AI 解决数学领域难题!