📌  相关文章
📜  将前缀和后缀乘以-1后最大化数组的总和(1)

📅  最后修改于: 2023-12-03 15:25:16.858000             🧑  作者: Mango

将前缀和后缀乘以-1后最大化数组的总和

介绍

该问题为一个常见的题型,通常被称为最大翻转子段和(Maximum Flipping Subarray Sum)问题。

给定一个长度为n的整数数组a,将其某个连续子段[l,r]中的数字进行翻转,即将子段[l,r]内的每个数字乘以-1,而其他位置的数字不变。

例如,对于数组a=[1, -2, 3, -4, 5],若选择子段[2,4]进行翻转,则得到数组a'=[1, 2, -3, 4, 5],这个操作使子段[2,4]内的数字乘以-1。

目标是找出一个连续子段,翻转该子段中的数字以使得整个数组的元素之和最大。具体而言,目标是找出[l,r],使得a'[i]=a[i] (l<=i<=r) 且

sum(a[l..r]) + sum(a[1..l-1]) - sum(a[l..r]) + sum(a[r+1..n]) - sum(a[l..r])

或者等价的

2(sum(a[1..l-1]) - sum(a[l..r]) + sum(a[r+1..n]))

的值最大。

思路

首先,我们可以注意到题目中的两个式子等价,我们在后面的讨论中直接使用后者。

将公式中的变量进行变形,把l和r分离:

2(sum(a[1..l-1]) + sum(a[r+1..n]))-2sum(a[l..r])

我们可以把问题转化成两个子问题,求出 sum(a[1..l-1])+sum(a[r+1..n]) 的最小值和 sum(a[l..r]) 的最大值。

考虑如何解决这两个子问题。

求取 sum(a[l..r]) 的最大值

观察a[l..r]的形式,可以发现一种易于处理的形式。

假设我们从头开始遍历a,同时记录一下从头开始到目前为止所累加的和。我们从头到尾扫一遍,记录当前的前缀和,并将其与前面所遍历的前缀和的最小值作差。如果差值是最大的,那么就找到了我们所需要的子段[l,r]。

通过上述过程,我们实际上将 sum(a[l..r]) 分解成了一个前缀和减去一个前缀和的最小值。

下面是对应的代码实现(使用Kadane's算法):

def max_subarray(a):
    cur_sum = 0
    max_sum = 0
    for x in a:
        cur_sum = max(x, cur_sum + x)
        max_sum = max(max_sum, cur_sum)
    return max_sum
求取 sum(a[1..l-1])+sum(a[r+1..n]) 的最小值

观察 a[1..l-1] 和 a[r+1..n] 的形式,可以发现它们是两个独立的数组。我们只需考虑如何处理一个数组就好了。下面以处理a[1..l-1]为例。

形式化地,我们需要求解:

sum(a[1..l-1]) + sum(a[r+1..n]) = sum(a) - sum(a[l..r])

将问题转化成求取sum(a[l..r])的最大值是不一定有优势的。下面提供一种解法,能够直接处理这个问题。

假设我们从头开始遍历a,并把从头开始到目前为止所累加的和存储到一个变量中(称之为curr_sum)。我们从头到尾扫一遍,记录当前的curr_sum,并将其与前面所遍历的curr_sum的最小值作差。如果差值是最大的,那么就找到了我们所需要的子段[l,r]。

通过上述过程,我们实际上将 sum(a[1..l-1]) 分解成了一个后缀和减去一个后缀和的最小值。

下面是对应的代码实现(同样是使用Kadane's算法):

def min_subarray(a):
    cur_sum = 0
    min_sum = 0
    for x in a:
        cur_sum = min(x, cur_sum + x)
        min_sum = min(min_sum, cur_sum)
    return min_sum
总结

我们将原问题分解成了两个子问题:

  • 求取 sum(a[l..r]) 的最大值
  • 求取 sum(a[1..l-1])+sum(a[r+1..n]) 的最小值

可以使用Kadane's算法来解决这两个问题,并且两个问题的时间复杂度均为O(n)。

最终的结果等于 2* (sum(a[1..l-1]) - sum(a[l..r]) + sum(a[r+1..n])),其中 sum(a[1..l-1]) 和 sum(a[r+1..n]) 可以用 min_subarray 来解决,sum(a[l..r]) 可以用 max_subarray 来解决。

实现代码如下:

def max_flipping_subarray_sum(a):
    l_max_sum = max_subarray(a)
    r_max_sum = max_subarray(a[::-1])[::-1]
    left_min_sum = min_subarray(a[:-1])
    right_min_sum = min_subarray(a[1:])
    return 2 * (max(left_min_sum, 0) - l_max_sum + max(right_min_sum, 0) - r_max_sum + sum(a))
参考
  1. 算法竞赛进阶指南(红皮书)
  2. Max Flipping Subarray Sum - GeeksforGeeks