将O(n^2)
算法优化为O(n log n)
。
问题陈述
给定A
个正整数的数组n
。将数组划分为长度不大于k
的连续子序列,使得每个子序列的最大值之和最小。这是一个例子。
如果n = 8
和k = 5
以及数组元素为1 4 1 3 4 7 2 2
,则最佳解决方案为1 | 4 1 3 4 7 | 2 2
。总和为max{1} + max{4, 1, 3, 4, 7} + max{2, 2} = 1 + 7 + 2 = 10
。
O(n ^ 2)解决方案
让dp[i]
成为子问题数组A[0] ... A[i]
的问题陈述中的最小和。 dp[0] = A[0]
和0 < i < n
(dp[-1] = 0
),
// A, n, k, - defined
// dp - all initialized to INF
dp[0] = A[0];
for (auto i = 1; i < n; i++) {
auto max = -INF;
for (auto j = i; j >= 0 && j >= i-k+1; j--) {
if (A[j] > max)
max = A[j];
auto sum = max + (j > 0 ? dp[j-1] : 0);
if (sum < dp[i])
dp[i] = sum;
}
}
// answer: dp[n-1]
O(n log n)?
问题作者声称可以在O(n log n)
时间内解决这个问题,并且有些人能够通过测试用例。如何优化?
答案 0 :(得分:3)
注意:我会略微改变您的动态编程关系,因此j = 0
没有特殊情况。现在dp[j]
是第一个j
条A[0], ..., A[j-1]
和
dp[i] = min(dp[j] + max(A[j], ..., A[i-1]), i-k <= j < i)
问题的答案现在是dp[n]
。
请注意,如果j < i
和dp[j] >= dp[i]
,您在以下转换中不需要dp[j]
,因为max(A[j], ..., A[l]) >= max(A[i], ..., A[l])
(因此,切换到{时总是更好{1}}代替i
。
此外,让j
(其中C[j] = max(A[j+1], ..., A[l])
是动态编程步骤中的当前索引,即C ++程序中的l
。
然后你可以在内存中保留一些索引i
(动态编程关系转换的“有趣”索引),这样:x1 < ... < xm
(1)。然后自动dp[x1] < ... < dp[xm]
(2)。
要存储C[x1] >= ... >= C[xm]
,我们需要一些支持以下操作的数据结构:
{x1, ..., xm}
移动到i
时,我们必须说i+1
现在无法访问)或前面(参见插入)。i-k
(当我们计算x
时,我们在保留(1)的同时插入它,删除相应的元素。)dp[i]
。因此,一些将min(dp[xj] + C[xj], 1 <= j <= m)
与x1, ..., xk
一起存储以存储所有set
的队列就足够了。
当我们插入元素dp[xi] + C[xi]
时,我们如何保留(1)和更新C
?
i
之前,我们使用dp[i]
更新C
。为此,我们在集合A[i-1]
s.t中找到最小元素xj
。 x
。然后(1)和(2)暗示所有C[xj] <= A[i-1]
dp[j'] + C[j'] >= dp[j] + C[j]
,我们将j' >= j
更新为C[xj]
,然后从集合中删除A[i-1]
(*) x(j+1), ..., xm
时,我们只删除所有元素s.t.弹出前面dp[i]
。dp[j] >= dp[i]
时,(*)中被破坏的某个元素现在可能变得最好。因此,如有必要,我们会更新i-k
并插入最后一个元素。复杂性:C
(集合中最多可插入O(n log n)
个。)
此代码总结了主要观点:
2n
这次我对你的算法进行了压力测试:see here。
如果目前还不清楚,请告诉我。