最大子数组和问题的变种——最多允许翻转一个子数组的最大子数组和
字数 1648 2025-11-11 15:27:17

最大子数组和问题的变种——最多允许翻转一个子数组的最大子数组和

题目描述
给定一个整数数组nums,你可以选择翻转数组中的任意一个连续子数组(即将该子数组的元素顺序反转),然后返回可能得到的最大子数组和。注意,你最多只能进行一次翻转操作,也可以选择不翻转。

示例:
输入:nums = [1, -2, 3, -2, 4]
输出:8
解释:翻转子数组[-2, 3, -2]得到[1, 3, -2, 3, 4],最大子数组和为3 + (-2) + 3 + 4 = 8

解题过程

这个问题可以分解为几个关键步骤,我们需要分别处理不翻转和翻转一次的情况。

1. 基础情况:不翻转
首先,我们需要计算原数组的最大子数组和。这可以通过经典的Kadane算法实现:

  • 定义dp[i]为以第i个元素结尾的最大子数组和
  • 状态转移方程:dp[i] = max(nums[i], dp[i-1] + nums[i])
  • 最终结果是所有dp[i]中的最大值

2. 翻转一次的情况分析
当我们翻转一个子数组nums[i...j]时,实际上相当于:

  • 保持nums[0...i-1]不变
  • 将nums[i...j]反转
  • 保持nums[j+1...n-1]不变

关键观察:翻转nums[i...j]后,最大子数组和可能出现在:

  • 完全在翻转区间内
  • 跨越翻转区间边界
  • 完全不涉及翻转区间

但更有效的方法是考虑翻转操作对最大子数组和的影响。

3. 预处理数组
为了高效处理翻转情况,我们需要预处理四个数组:

left_max[i]:表示从数组开头到位置i(包括i)的最大子数组和

  • 计算方法:从左到右应用Kadane算法,记录每个位置的最大值

right_max[i]:表示从位置i到数组末尾的最大子数组和

  • 计算方法:从右到左应用Kadane算法

left_sum[i]:表示从开头到位置i的数组元素和

  • 用于快速计算区间和

right_sum[i]:表示从位置i到末尾的数组元素和

4. 翻转情况的最大子数组和计算
对于每个可能的分割点i,考虑翻转操作跨越i的情况:

  • 翻转区间为[k, i-1],其中k < i
  • 翻转后的最大子数组和 = left_max[k] + (right_sum[i] - left_sum[i-1]) + 某些项

更精确的公式:
对于每个位置i,最大可能和 = left_max[i] + max(0, 某个右侧最大和)

5. 完整算法步骤

  1. 计算不翻转时的最大子数组和(基础情况)
  2. 预处理left_max, right_max, left_sum, right_sum数组
  3. 对于每个位置i,计算:
    • 以i为分割点的最大可能和
    • 考虑翻转区间完全在左侧、完全在右侧、跨越i的情况
  4. 取所有情况的最大值

6. 时间复杂度优化
直接枚举所有可能的翻转区间是O(n²)的,我们需要优化到O(n)。

关键技巧:同时维护多个状态变量,在一次遍历中计算所有可能情况。

7. 最终解决方案
定义:

  • no_rev:不翻转时的最大子数组和
  • max_left[i]:从0到i的最大子数组和
  • max_right[i]:从i到n-1的最大子数组和
  • total_left[i]:从0到i的数组元素和
  • total_right[i]:从i到n-1的数组元素和

对于每个位置i,考虑翻转区间结束于i-1的情况:
翻转后的最大和 = max_left[i] + max(0, max_right[i] - (total_right[i] - total_left[i-1]))

8. 完整代码框架

function maxSubarrayAfterFlip(nums):
    n = len(nums)
    if n == 0: return 0
    
    # 基础情况:不翻转
    max_no_flip = kadane(nums)
    
    # 预处理数组
    left_max = [0] * n
    right_max = [0] * n
    left_sum = [0] * n
    right_sum = [0] * n
    
    # 填充预处理数组...
    
    # 计算翻转一次的最大值
    max_with_flip = -float('inf')
    for i in range(n):
        # 计算以i为关键点的各种翻转情况
        # 更新max_with_flip
    
    return max(max_no_flip, max_with_flip)

9. 边界情况处理

  • 空数组:返回0
  • 全负数数组:返回最大单个元素
  • 全正数数组:返回数组总和

这个问题的核心在于将翻转操作转化为对数组分段的重新组合,通过预处理和状态维护将时间复杂度优化到O(n)。

最大子数组和问题的变种——最多允许翻转一个子数组的最大子数组和 题目描述 给定一个整数数组nums,你可以选择翻转数组中的任意一个连续子数组(即将该子数组的元素顺序反转),然后返回可能得到的最大子数组和。注意,你最多只能进行一次翻转操作,也可以选择不翻转。 示例: 输入:nums = [ 1, -2, 3, -2, 4 ] 输出:8 解释:翻转子数组[ -2, 3, -2]得到[ 1, 3, -2, 3, 4 ],最大子数组和为3 + (-2) + 3 + 4 = 8 解题过程 这个问题可以分解为几个关键步骤,我们需要分别处理不翻转和翻转一次的情况。 1. 基础情况:不翻转 首先,我们需要计算原数组的最大子数组和。这可以通过经典的Kadane算法实现: 定义dp[ i ]为以第i个元素结尾的最大子数组和 状态转移方程:dp[ i] = max(nums[ i], dp[ i-1] + nums[ i ]) 最终结果是所有dp[ i ]中的最大值 2. 翻转一次的情况分析 当我们翻转一个子数组nums[ i...j ]时,实际上相当于: 保持nums[ 0...i-1 ]不变 将nums[ i...j ]反转 保持nums[ j+1...n-1 ]不变 关键观察:翻转nums[ i...j ]后,最大子数组和可能出现在: 完全在翻转区间内 跨越翻转区间边界 完全不涉及翻转区间 但更有效的方法是考虑翻转操作对最大子数组和的影响。 3. 预处理数组 为了高效处理翻转情况,我们需要预处理四个数组: left_ max[ i] :表示从数组开头到位置i(包括i)的最大子数组和 计算方法:从左到右应用Kadane算法,记录每个位置的最大值 right_ max[ i] :表示从位置i到数组末尾的最大子数组和 计算方法:从右到左应用Kadane算法 left_ sum[ i] :表示从开头到位置i的数组元素和 用于快速计算区间和 right_ sum[ i] :表示从位置i到末尾的数组元素和 4. 翻转情况的最大子数组和计算 对于每个可能的分割点i,考虑翻转操作跨越i的情况: 翻转区间为[ k, i-1],其中k < i 翻转后的最大子数组和 = left_ max[ k] + (right_ sum[ i] - left_ sum[ i-1 ]) + 某些项 更精确的公式: 对于每个位置i,最大可能和 = left_ max[ i ] + max(0, 某个右侧最大和) 5. 完整算法步骤 计算不翻转时的最大子数组和(基础情况) 预处理left_ max, right_ max, left_ sum, right_ sum数组 对于每个位置i,计算: 以i为分割点的最大可能和 考虑翻转区间完全在左侧、完全在右侧、跨越i的情况 取所有情况的最大值 6. 时间复杂度优化 直接枚举所有可能的翻转区间是O(n²)的,我们需要优化到O(n)。 关键技巧:同时维护多个状态变量,在一次遍历中计算所有可能情况。 7. 最终解决方案 定义: no_ rev:不翻转时的最大子数组和 max_ left[ i ]:从0到i的最大子数组和 max_ right[ i ]:从i到n-1的最大子数组和 total_ left[ i ]:从0到i的数组元素和 total_ right[ i ]:从i到n-1的数组元素和 对于每个位置i,考虑翻转区间结束于i-1的情况: 翻转后的最大和 = max_ left[ i] + max(0, max_ right[ i] - (total_ right[ i] - total_ left[ i-1 ])) 8. 完整代码框架 9. 边界情况处理 空数组:返回0 全负数数组:返回最大单个元素 全正数数组:返回数组总和 这个问题的核心在于将翻转操作转化为对数组分段的重新组合,通过预处理和状态维护将时间复杂度优化到O(n)。