可以为我们提供最大“触发器”总和的子列表数组是啥?

Posted

技术标签:

【中文标题】可以为我们提供最大“触发器”总和的子列表数组是啥?【英文标题】:What is the sublist array that can give us maximum 'flip-flop' sum?可以为我们提供最大“触发器”总和的子列表数组是什么? 【发布时间】:2020-01-01 13:12:47 【问题描述】:

我的问题是我得到了一个长度为 l 的数组。

假设这是我的数组:[1,5,4,2,9,3,6] 我们称之为A

这个数组可以有多个节点相邻的子数组。所以我们可以有[1,5,4][2,9,3,6] 等等。每个子数组的长度无关紧要。

但诀窍在于求和部分。我们不能只添加所有数字,它就像触发器一样工作。所以对于子列表[2,9,3,6],总和将是[2,-9,3,-6],即:-10。而且很小。 产生最大总和的数组 A 的子列表(或您喜欢的子数组)是什么? 一种可能的方法是(从直觉上)子列表[4,2,9] 将输出一个不错的结果:[4, -2, 9] =(添加所有元素)=11

问题是,如何得出这样的结果? 给我们最大触发器和的子数组是什么?

主要是什么算法,以任意数组为输入,输出一个所有数相邻且总和最大的子数组?

我没有想出任何办法,但我很确定我应该选择动态编程或分而治之来解决这个问题。再说一遍,我不知道,我可能完全错了。

【问题讨论】:

【参考方案1】:

这个问题确实可以使用动态规划来解决,方法是跟踪每个位置的最大总和。

但是,由于当前元素可以添加到总和中,也可以从总和中减去(取决于子序列的长度),我们将分别跟踪以此处结尾的最大总和,两者也一样作为奇数子序列长度

下面的代码(在 python 中实现)就是这样做的(更多细节请参见代码中的 cmets)。 时间复杂度为 O(n)

a = [1, 5, 4, 2, 9, 3, 6]

# initialize the best sequences which end at element a[0]
# best sequence with odd length ending at the current position
best_ending_here_odd = a[0] # the sequence sum value
best_ending_here_odd_start_idx = 0
# best sequence with even length ending at the current position
best_ending_here_even = 0   # the sequence sum value
best_ending_here_even_start_idx = 1

best_sum = 0
best_start_idx = 0
best_end_idx = 0
for i in range(1, len(a)):
    # add/subtract the current element to the best sequences that
    # ended in the previous element
    best_ending_here_even, best_ending_here_odd = \
        best_ending_here_odd - a[i], best_ending_here_even + a[i]

    # swap starting positions (since a sequence which had odd length when it
    # was ending at the previous element has even length now, and vice-versa)
    best_ending_here_even_start_idx, best_ending_here_odd_start_idx = \
        best_ending_here_odd_start_idx, best_ending_here_even_start_idx

    # we can always make a sequence of even length with sum 0 (empty sequence)
    if best_ending_here_even < 0:
        best_ending_here_even = 0
        best_ending_here_even_start_idx = i + 1

    # update the best known sub-sequence if it is the case
    if best_ending_here_even > best_sum:
        best_sum = best_ending_here_even
        best_start_idx = best_ending_here_even_start_idx
        best_end_idx = i
    if best_ending_here_odd > best_sum:
        best_sum = best_ending_here_odd
        best_start_idx = best_ending_here_odd_start_idx
        best_end_idx = i

print(best_sum, best_start_idx, best_end_idx)

对于问题中的示例序列,上述代码输出如下触发器子序列:

4 - 2 + 9 - 3 + 6 = 14

【讨论】:

请您确定合并、分治步骤是什么?我们如何分析这个以及用什么方法来达到 O(n) 的复杂度? @Aliz:在这种动态方法中解决的子问题如下:“以元素 i 结尾的最佳子序列是什么,分别具有奇数和偶数长度” , 对于每个索引 i。这意味着我们正在解决 2 * n 个子问题。但是每个这样的子问题都在 O(1) 中通过使用前一个 i 的解决方案来解决。因此,时间复杂度为 O(n)。【参考方案2】:

正如 quertyman 所写,我们可以使用动态编程。这类似于 Kadane 的算法,但有一些曲折。我们需要第二个临时变量来跟踪每个元素作为加法和减法的尝试。请注意,减法必须在加法之前,反之则不然。 O(1) 空间,O(n) 时间。

javascript 代码:

function f(A)
  let prevAdd = [A[0], 1] // sum, length
  let prevSubt = [0, 0]
  let best = [0, -1, 0, null] // sum, idx, len, op
  let add
  let subt
  
  for (let i=1; i<A.length; i++)
    // Try adding
    add = [A[i] + prevSubt[0], 1 + prevSubt[1]]
  
    if (add[0] > best[0])
      best = [add[0], i, add[1], ' + ']
      
    // Try subtracting
    if (prevAdd[0] - A[i] > 0)
      subt = [prevAdd[0] - A[i], 1 + prevAdd[1]]
    else
      subt = [0, 0]
    
    if (subt[0] > best[0])
      best = [subt[0], i, subt[1], ' - ']
      
    prevAdd = add
    prevSubt = subt
  
  
  return best


function show(A, sol)
  let [sum, i, len, op] = sol
  let str = A[i] + ' = ' + sum
  for (let l=1; l<len; l++)
    str = A[i-l] + op + str
    op = op == ' + ' ? ' - ' : ' + '
  
  return str


var A = [1, 5, 4, 2, 9, 3, 6]
console.log(JSON.stringify(A))
var sol = f(A)
console.log(JSON.stringify(sol))
console.log(show(A, sol))

更新

根据 OP 在 cmets 中的要求,这里有一些关于一般递归(伪代码)的理论阐述:让 f(i, subtract) 表示最大总和,包括在 i 处索引的元素,其中 subtract 表示是否或不是元素被减去或添加。那么:

// Try subtracting
f(i, true) =
  if f(i-1, false) - A[i] > 0
  then f(i-1, false) - A[i]
  otherwise 0

// Try adding
f(i, false) =
  A[i] + f(i-1, true)

(Note that when f(i-1, true) evaluates
 to zero, the best ending at
 i as an addition is just A[i])

循环只依赖于前一个元素的求值,这意味着我们可以用O(1)空格对其进行编码,只需在每次迭代后保存最后的求值,并更新迄今为止最好的(包括序列的结束索引和长度,如果我们愿意)。

【讨论】:

你能想出一个伪代码或者把它转换成一个吗?由于我对算法和编程世界非常陌生,因此我很难理解这背后的逻辑。

以上是关于可以为我们提供最大“触发器”总和的子列表数组是啥?的主要内容,如果未能解决你的问题,请参考以下文章

c_cpp 最大子阵列总和。在具有最大总和的数组(包含至少一个数字)中查找连续的子数组。

最大子阵列和

2021-07-16:三个无重叠子数组的最大和。给定数组 nums 由正整数组成,找到三个互不重叠的子数组的最大和。每个子数组的长度为k,我们要使这3*k个项的和最大化。返回每个区间起始索引的列表(索

数字总和可被 K 整除的子数组的数量

总和小于 M 的大小为 K 的子集的最大总和

最大和子数组 - 返回子数组和总和 - 分而治之