以步长达到阵列结尾的最高得分

时间:2018-07-31 17:31:19

标签: arrays algorithm dynamic-programming

假设一个数组,我们从索引为0的元素开始。我们希望通过最大长度为K的步长从0索引到数组的最后一个索引。

例如,假设一个数组为[10,2,-10,5,20],K为2,这意味着最大步长为2(我们可以假设K总是可能的,并且小于数组的长度)。 现在,我们从索引0开始,当前的分数是10,然后我们可以升至2或可以升至-10。假设我们从此处转到2,所以总分变为10 + 2 = 12。现在从2开始,我们可以达到-10或5,所以您进入5时得分为12 + 5 = 17。从这里您可以直接转到最后一个索引,因为除此之外别无选择,因此总分是17 + 20 = 37。

对于给定的长度为N和整数K的数组,我们需要找到可以获得的最大分数。 我想到了一个解决方案,通过确定天气是否达到索引i来将其分为子问题,然后递归调用其余数组。但是我从这个问题中感觉到了一些动态编程。

如何解决给定大小为N和整数K的数组。

约束:1 <= N <= 100000和1 <= K <= N

5 个答案:

答案 0 :(得分:1)

了解O(n*k)解决方案。 主函数调用为findMax(A,N,K,0)。

MAX = new Array();
MAX[i] = null. For 0<=i<N

null表示尚未填充特定元素。

procedure findMax(A,N,K,i)
 {
  if (MAX[i]!=null)
   return MAX[i];
  else if (i==N-1)
   MAX[i]=A[i];
  else
   {
    MAX[i]=A[i]+findMax(A,N,K,i+1);

    for (j=2; j<=K&&(i+j)<N; ++j)
     if (A[i]+findMax(A,N,K,i+j)>MAX[i])
      MAX[i]=A[i]+findMax(A,N,K,i+j);
   }

  return MAX[i];
 }

该问题具有最佳的子结构属性。为了计算最佳解,需要计算所有子问题。因此,乍一看,我想时间复杂度不会低于O(n*k)

答案 1 :(得分:1)

尝试以这种方式向后走,可以在 O(n * logk)中实现。

如果数组的大小为1,则最大值为该元素。在i元素中考虑您-您可以选择他或下一个K元素之一->选择一个使最终结果最大化的元素。

考虑以下伪代码:

基于@RandomPerfectHashFunction答案并作一些更改

将Max视为我们的答案数组,将树视为AVL Tree(自平衡二进制搜索树)

findMaxStartingFromIndex(A,N,K,i, Max, Tree)
    if Max[i] != null
        return Max[i]
    max = Tree.Max // log k - just go down all the way to the right 
    if (i + k > N) // less then k element to end of array
        max = max(max,0) // take the maximum only if he positive 
    Max[i] = A[i] + max
    Tree.add(Max[i])
    if (i + k < N)
        Tree.remove(Max[i+k]) // remove the element from tree because it is out of the rolling-window of k elements
    return Max[i]

在主屏幕中:

Init Max array at size N
Init Tree as empty AVL tree
Max[N-1] = A[N-1]
Tree.add(MAX[N-1])
for (i = N-2; i >= 0 ; i--)
    findMaxStartingFromIndex(A,N,K,i, Nax, Tree)

完成所有操作后,将在 Max 数组的前k个元素中查找最大值(并非总是选择第一个元素是最佳选择)

在二叉搜索树中添加查找和删除元素为log n->在我们的情况下,树仅容纳k个元素->我们实现了O(n * logk)复杂度

答案 2 :(得分:1)

这可以在O(n) time and memory

中解决

基本上:从i = n-1回到0,您必须知道从i + 1到i + k的最佳索引是什么?那么对我来说最好的答案就是跳到[i + 1,i + k]范围内的最佳索引

要获取该信息,您可以创建某种队列(但是您需要能够在c ++中从双方执行弹出操作,您可以使用出队)。 在该队列中,您保留两个信息:(时间,值),其中时间是推入元素的时间,而值是从元素开始获得的最佳总和。

现在,当您进入索引i:第一次弹出直到当前时间(最小命名为t)减去queue.top.time为> k:while( t-que.top.time > k) que.pop

然后,您可以获取que.top.value + array[i],这是从索引i中可以获得的最佳值。

最后要做的是更新队列。您创建新元素e = (t, que.top.value + array[i])并执行que.back(而不是que.top)并执行

while (que.back.value <= e.value) que.pop_back

然后您可以后退

que.push_back(e)

增加t ++

之所以起作用,是因为当您的新元素具有更高的价值时,过去插入到que上的元素最好保留该元素,因为您将可以使用更长的时间。

希望有道理:)

答案 3 :(得分:0)

这可以在O(n)中完成。我假设您已经熟悉基本的DP算法,该算法在O(nk)中运行。我们有dp[i] = value[i] + (max(dp[j]) for i - k < j < i)k复杂性的因素来自于在DP数组中找到最后k个值中的最小值,我们可以将其优化为O(1)

一种优化可能是维护一个包含最后k个值的二进制搜索树,这将成为一个O(n log k)解决方案。但是我们可以使用双端队列而不是二进制搜索树来做得更好。

我们维护一个双端队列,其中包含最后k个元素中的最大值的候选对象。在将当前dp值放入双端队列的后面之前,如果该值小于或等于当前值,则从后面弹出该值。因为当前值都比后边的值更好(或至少一样好),并且在双端队列中的时间更长,所以后侧的值永远不会是双端队列中的最大值,可以将其丢弃。重复此操作,直到后面的值不再小于或等于当前值为止。

如果索引值小于当前索引值k,我们可以弹出该值。

我们从后面弹出数字的方式使队列一直在减少,因此最大值在前面。

请注意,即使在主循环的迭代中,从后面弹出值的循环可能运行多达n - 1次,但总复杂度仍然为O(n),因为DP阵列最多弹出一次。

答案 4 :(得分:0)

这可以通过动态规划解决。 dp[i] 表示我们可以从 nums[0] 到 nums[i] 收集的最大分数。过渡是 dp[i] = max(dp[i-1], dp[i-2],...,dp[i-k])+nums[i]。时间复杂度为 O(nk)。