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

时间:2011-12-21 15:21:33

标签: arrays algorithm split partition-problem

我过去曾经遇到过类似的问题,我仍然不知道如何解决这个问题。问题是这样的:

您将获得一个正整数数组,其大小为n <= 1000且k <= n,这是您必须将数组拆分为的连续子数组的数量。你必须输出最小m,其中m = max {s [1],...,s [k]},s [i]是第i个子阵列的总和。数组中的所有整数都在1到100之间。例如:

Input:                           Output:
5  3  >> n = 5 k = 3             3
2 1 1 2 3

将数组拆分为2 + 1 | 1 + 2 | 3将最小化m。

我的蛮力想法是让第一个子阵列在位置i(对于所有可能的i)结束,然后尝试以尽可能最好的方式将数组的其余部分分成k-1个子阵列。但是,这是指数级解决方案,永远不会奏效。

所以我正在寻找好的想法来解决它。如果你有,请告诉我。

感谢您的帮助。

6 个答案:

答案 0 :(得分:5)

您可以使用动态编程来解决此问题,但实际上您可以通过贪婪和二元搜索来解决问题。此算法的复杂度为O(n log d),其中d是输出答案。 (上限是数组中所有元素的总和。)(或输出位大小为O( n d )

我们的想法是二元搜索你的m - 然后贪婪地向前推进数组,将当前元素添加到分区,除非添加当前元素将其推送到当前{{1} - 在这种情况下,您启动一​​个新分区。如果使用的分区数小于或等于给定输入m,则当前m成功(因此调整上限)。否则,您使用了太多分区,并在k上提高了下限。

一些伪代码:

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 } 不是太大,您实际上可以跳过二分查找并进行线性搜索:

d

答案 1 :(得分:3)

动态编程。制作一个数组

int best[k+1][n+1];

其中best[i][j]是最好的,你可以实现分割数组int j子数组的第一个i元素。 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)好。

答案 2 :(得分:2)

我有一个糟糕的分支和绑定算法(请不要低估我)

首先将数组和dvide的总和取为k,这为你提供了最佳的答案范围,即平均值A.此外,我们将保留迄今为止任何分支GO(全局最优)的最佳解决方案。让我们考虑一下我们在一些数组元素之后放置一个分隔符(逻辑)作为分区单元,我们必须放置k-1个分区。现在我们将以这种方式贪婪地放置分区,

遍历数组元素,将它们相加,直到您看到在下一个位置我们将超过A,现在将两个分支放在一个将分隔符放在此位置的位置,另一个放置在下一个位置的位置,执行此操作并递归GO = min(GO,回答分支)。 如果在任何分支的任何一点,我们有一个更大的分区,那么GO或位置的数量小于那么分区留给我们绑定。最后,当你回答时,你应该有GO。

修改 正如丹尼尔所建议的那样,我们可以稍微修改分配器放置策略,直到你将元素总和作为A或剩下的剩余位置小于分隔符。

答案 3 :(得分:1)

这只是一个想法的草图......我不确定它是否有效,但它很容易(也可能很快)。

你开始说分离均匀分布(实际上你的开始并不重要)。

制作每个子阵列的总和 找到总和最大的子阵列 如果左侧的子阵列的总和低于右侧的子阵列(反之亦然),请查看右侧和左侧的邻居子阵列并将左侧的分隔移动一个。
用当前最大总和重做子阵列。

你会遇到一些情况,你会在相同的两个位置之间保持分离,这可能意味着你有解决方案。

编辑:请参阅@rds的评论。你必须更加努力地思考弹跳解决方案和最终条件。

答案 4 :(得分:0)

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

从那里

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

答案 5 :(得分:0)

我的想法,遗憾的是不起作用:

  1. 将数组拆分为N个子阵列
  2. 找到总和最小的两个连续子阵列
  3. 合并步骤2中找到的子阵列以形成新的连续子阵列
  4. 如果子阵列的总数大于k,则从步骤2开始迭代,否则完成。