需要解决这个算法难题的想法

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 &gt;= 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 | 32 | 1 1 2 | 3 结束。然后,您需要检查是否可以通过移动边界来减小最大值。它可能会为您提供移动边界的良好起始位置,并且在大多数情况下,您会很快找到最佳位置,但我不确定是否存在您会陷入局部但不是全局最小值的情况. 是的,两个小值接近的问题似乎总是会导致局部最小值,因为算法会将它们合并在一起,而不是合并到更接近的值。【参考方案6】:

如果你的数组有随机数,你可以希望每个子数组都有 n/k 的分区是一个很好的起点。

从那里

    通过计算总和来评估此候选解决方案 存储此候选解决方案。例如: 每个子数组的索引数组 子数组求和的对应最大值 减小最大子数组的大小:创建两个新的候选者:一个子数组从 index+1 开始;一个以 index-1 结尾的子数组 评估新候选人。 如果它们的最大值更高,则丢弃 如果它们的最大值较低,则在 2 上迭代,除非该候选者已被评估,在这种情况下它就是解决方案。

【讨论】:

以上是关于需要解决这个算法难题的想法的主要内容,如果未能解决你的问题,请参考以下文章

我需要解决一个 NP 难题。有希望吗?

有没有办法测量这个算法解决这个 Soduku 拼图所花费的时间?

Count-Min Sketch 算法,解决大数据统计难题

强化学习发现矩阵乘法算法!DeepMind登Nature封面,用 AI 解决数学领域难题!

强化学习发现矩阵乘法算法!DeepMind登Nature封面,用 AI 解决数学领域难题!

互助推广APP免费解决自媒体推广难题