最大子段和的变种:允许至多删除一个元素的最大子段和
字数 2879 2025-12-06 10:38:57

最大子段和的变种:允许至多删除一个元素的最大子段和

题目描述
给定一个整数数组 nums,你需要求出其最大子段和。这里子段指的是数组中连续的一段非空子数组。不过,与经典的最大子段和问题不同,本题允许你至多删除一个元素(也可以不删除)来得到总和最大的子段。你需要计算在所有可能的删除情况(包括不删除)下,能获得的最大子段和。

例如:

  • 输入:nums = [1, -2, 0, 3]
  • 输出:4
  • 解释:可以选择子段 [1, -2, 0, 3] 并删除 -2,得到 [1, 0, 3],其和为 4。不删除任何元素时,最大子段和为 3(子段 [3][0, 3]),删除后能获得更大和。

解题思路
这是一个经典的线性动态规划变种问题。核心在于,当我们允许删除一个元素时,实际上是允许子段在某个位置“跳过”一个元素,但子段其余部分必须连续。我们可以通过定义两个状态数组来递推计算。

步骤详解

  1. 定义状态
    dp0[i] 表示以第 i 个元素结尾,且没有删除任何元素的最大子段和。
    dp1[i] 表示以第 i 个元素结尾,且已经删除了一个元素的最大子段和。
    这里“以第 i 个元素结尾”意味着子段必须包含 nums[i],这样我们才能方便地递推。

  2. 状态转移方程

    • 对于 dp0[i](没有删除过元素):
      它要么是只包含当前元素 nums[i],要么是接在前一个未删除元素的子段后面,即:
      dp0[i] = max(nums[i], dp0[i-1] + nums[i])
      这其实就是经典的最大子段和的递推公式。

    • 对于 dp1[i](已经删除过一个元素):
      这里有两种可能:
      a. 删除操作发生在当前元素之前,即当前元素 nums[i] 必须被保留,而前面的某个元素被删除了。那么,dp1[i] 可以由 dp1[i-1] + nums[i] 得到(因为已经删除过,现在只能继续保留当前元素)。
      b. 删除操作就是删除当前元素 nums[i] 本身。那么,以 nums[i] 结尾且删除了它,实际上相当于子段以 nums[i-1] 结尾且没有删除过,即 dp0[i-1]
      综合两者:
      dp1[i] = max(dp1[i-1] + nums[i], dp0[i-1])
      注意:第二种情况中,dp0[i-1] 可能为负,但即使为负,删除当前元素后子段和就是 dp0[i-1](相当于子段只到 i-1i 被删除,子段不能为空,所以至少保留 i-1 这个元素)。

  3. 初始条件
    我们需要考虑第一个元素 i=0 的情况:

    • dp0[0] = nums[0]:以第一个元素结尾且没删除,就是它自身。
    • dp1[0] = 0?这里需要仔细思考。dp1[0] 表示以第 0 个元素结尾且已经删除过一个元素。但如果我们删除了第 0 个元素,那么以第 0 个元素结尾的子段就不存在了(因为结尾元素被删了)。所以实际上,dp1[0] 应该是一个无效状态,但在递推中,我们可以将它初始化为一个非常小的数(比如 -inf),表示不可能。不过,更常见且简便的处理是:
      我们允许删除操作后子段可以为空吗?题目要求子段是非空的,但“删除一个元素”可能使得剩余部分为空吗?如果数组只有一个元素,删除它后子段为空,这是不允许的。因此,在只有单个元素时,不能应用删除操作。
      但为了递推方便,我们可以这样初始化:
      dp1[0] = -inf 或一个很小的数,这样在后续递推中,dp1[1] 会正确地由 dp0[0] 得到(即删除第二个元素,保留第一个)。
      在代码中,我们可以简单地将 dp1[0] 初始化为 0 吗?不可以,因为 dp1[0] 如果为 0,在递推 dp1[1] 时,dp1[0] + nums[1] 就变成了 nums[1],这表示“已经删除过元素且以第 1 个元素结尾”可以从“以第 0 个元素结尾且已删除”加上 nums[1] 得到,但 dp1[0] 本身是不合理的。所以稳妥起见,我们应设置 dp1[0] = -inf
      不过,由于我们最终会取所有 dp0[i]dp1[i] 的最大值,且 dp1[0] 无效,我们可以将 dp1[0] 初始化为一个非常小的数,比如 -10^9 以下,确保它不会被选为最大值。
  4. 递推计算
    i=1 开始遍历数组,按上述方程计算 dp0[i]dp1[i]
    同时,我们维护一个全局最大值 ans,每次更新 ans = max(ans, dp0[i], dp1[i])

  5. 边界情况

    • 数组长度为 1 时:只能选择这个元素,不能删除(删除后为空),所以答案就是 nums[0]
    • 数组全为负数时:我们可以选择绝对值最小的负数,或者删除它?实际上,如果全为负数,最大子段和就是最大的那个负数(不删除),因为删除一个元素并不会让和变大(删除后可能得到更小的负数或空)。我们的递推仍然能正确处理。
  6. 复杂度分析
    时间复杂度:O(n),只需遍历一次数组。
    空间复杂度:O(n),可以优化到 O(1) 只用几个变量。

示例推演
nums = [1, -2, 0, 3] 为例:

初始化:

  • dp0[0] = 1
  • dp1[0] = -inf(表示不可能)
  • ans = 1

i=1 (nums[1] = -2):

  • dp0[1] = max(-2, 1 + (-2)) = max(-2, -1) = -1
  • dp1[1] = max(dp1[0] + (-2), dp0[0]) = max(-inf, 1) = 1(这里对应删除当前元素 -2,保留前面的 1)
  • ans = max(1, -1, 1) = 1

i=2 (nums[2] = 0):

  • dp0[2] = max(0, -1 + 0) = max(0, -1) = 0
  • dp1[2] = max(dp1[1] + 0, dp0[1]) = max(1 + 0, -1) = 1
  • ans = max(1, 0, 1) = 1

i=3 (nums[3] = 3):

  • dp0[3] = max(3, 0 + 3) = 3
  • dp1[3] = max(dp1[2] + 3, dp0[2]) = max(1 + 3, 0) = 4
  • ans = max(1, 3, 4) = 4

最终答案为 4,与例子一致。

代码实现(Python)

def maximumSum(arr):
    n = len(arr)
    if n == 1:
        return arr[0]
    
    dp0 = [0] * n
    dp1 = [0] * n
    dp0[0] = arr[0]
    dp1[0] = -10**9  # 表示不可能
    
    ans = arr[0]
    for i in range(1, n):
        dp0[i] = max(arr[i], dp0[i-1] + arr[i])
        dp1[i] = max(dp1[i-1] + arr[i], dp0[i-1])
        ans = max(ans, dp0[i], dp1[i])
    
    return ans

空间优化
由于 dp0[i]dp1[i] 只依赖前一项,可以只用两个变量:

def maximumSum(arr):
    n = len(arr)
    if n == 1:
        return arr[0]
    
    dp0 = arr[0]
    dp1 = -10**9
    ans = arr[0]
    
    for i in range(1, n):
        new_dp0 = max(arr[i], dp0 + arr[i])
        new_dp1 = max(dp1 + arr[i], dp0)  # 注意这里 dp0 是旧的,即 dp0[i-1]
        ans = max(ans, new_dp0, new_dp1)
        dp0, dp1 = new_dp0, new_dp1
    
    return ans

总结
这个问题的关键是将“允许删除一个元素”转化为两种状态:是否已经删除过。通过分别维护“未删除”和“已删除”的最大子段和,我们可以在线性时间内解决问题。此方法清晰且高效,是处理此类变种问题的典型思路。

最大子段和的变种:允许至多删除一个元素的最大子段和 题目描述 给定一个整数数组 nums ,你需要求出其 最大子段和 。这里 子段 指的是数组中连续的一段非空子数组。不过,与经典的最大子段和问题不同,本题允许你 至多删除一个元素 (也可以不删除)来得到总和最大的子段。你需要计算在所有可能的删除情况(包括不删除)下,能获得的最大子段和。 例如: 输入: nums = [1, -2, 0, 3] 输出: 4 解释:可以选择子段 [1, -2, 0, 3] 并删除 -2 ,得到 [1, 0, 3] ,其和为 4 。不删除任何元素时,最大子段和为 3 (子段 [3] 或 [0, 3] ),删除后能获得更大和。 解题思路 这是一个经典的线性动态规划变种问题。核心在于,当我们允许删除一个元素时,实际上是允许子段在某个位置“跳过”一个元素,但子段其余部分必须连续。我们可以通过定义两个状态数组来递推计算。 步骤详解 定义状态 设 dp0[i] 表示以第 i 个元素结尾,且 没有删除任何元素 的最大子段和。 设 dp1[i] 表示以第 i 个元素结尾,且 已经删除了一个元素 的最大子段和。 这里“以第 i 个元素结尾”意味着子段必须包含 nums[i] ,这样我们才能方便地递推。 状态转移方程 对于 dp0[i] (没有删除过元素): 它要么是只包含当前元素 nums[i] ,要么是接在前一个未删除元素的子段后面,即: dp0[i] = max(nums[i], dp0[i-1] + nums[i]) 这其实就是经典的最大子段和的递推公式。 对于 dp1[i] (已经删除过一个元素): 这里有两种可能: a. 删除操作发生在当前元素之前,即当前元素 nums[i] 必须被保留,而前面的某个元素被删除了。那么, dp1[i] 可以由 dp1[i-1] + nums[i] 得到(因为已经删除过,现在只能继续保留当前元素)。 b. 删除操作就是删除当前元素 nums[i] 本身。那么,以 nums[i] 结尾且删除了它,实际上相当于子段以 nums[i-1] 结尾且没有删除过,即 dp0[i-1] 。 综合两者: dp1[i] = max(dp1[i-1] + nums[i], dp0[i-1]) 注意:第二种情况中, dp0[i-1] 可能为负,但即使为负,删除当前元素后子段和就是 dp0[i-1] (相当于子段只到 i-1 , i 被删除,子段不能为空,所以至少保留 i-1 这个元素)。 初始条件 我们需要考虑第一个元素 i=0 的情况: dp0[0] = nums[0] :以第一个元素结尾且没删除,就是它自身。 dp1[0] = 0 ?这里需要仔细思考。 dp1[0] 表示以第 0 个元素结尾且已经删除过一个元素。但如果我们删除了第 0 个元素,那么以第 0 个元素结尾的子段就不存在了(因为结尾元素被删了)。所以实际上, dp1[0] 应该是一个无效状态,但在递推中,我们可以将它初始化为一个非常小的数(比如 -inf ),表示不可能。不过,更常见且简便的处理是: 我们允许删除操作后子段可以为空吗?题目要求子段是非空的,但“删除一个元素”可能使得剩余部分为空吗?如果数组只有一个元素,删除它后子段为空,这是不允许的。因此,在只有单个元素时,不能应用删除操作。 但为了递推方便,我们可以这样初始化: dp1[0] = -inf 或一个很小的数,这样在后续递推中, dp1[1] 会正确地由 dp0[0] 得到(即删除第二个元素,保留第一个)。 在代码中,我们可以简单地将 dp1[0] 初始化为 0 吗?不可以,因为 dp1[0] 如果为 0,在递推 dp1[1] 时, dp1[0] + nums[1] 就变成了 nums[1] ,这表示“已经删除过元素且以第 1 个元素结尾”可以从“以第 0 个元素结尾且已删除”加上 nums[1] 得到,但 dp1[0] 本身是不合理的。所以稳妥起见,我们应设置 dp1[0] = -inf 。 不过,由于我们最终会取所有 dp0[i] 和 dp1[i] 的最大值,且 dp1[0] 无效,我们可以将 dp1[0] 初始化为一个非常小的数,比如 -10^9 以下,确保它不会被选为最大值。 递推计算 从 i=1 开始遍历数组,按上述方程计算 dp0[i] 和 dp1[i] 。 同时,我们维护一个全局最大值 ans ,每次更新 ans = max(ans, dp0[i], dp1[i]) 。 边界情况 数组长度为 1 时:只能选择这个元素,不能删除(删除后为空),所以答案就是 nums[0] 。 数组全为负数时:我们可以选择绝对值最小的负数,或者删除它?实际上,如果全为负数,最大子段和就是最大的那个负数(不删除),因为删除一个元素并不会让和变大(删除后可能得到更小的负数或空)。我们的递推仍然能正确处理。 复杂度分析 时间复杂度:O(n),只需遍历一次数组。 空间复杂度:O(n),可以优化到 O(1) 只用几个变量。 示例推演 以 nums = [1, -2, 0, 3] 为例: 初始化: dp0[0] = 1 dp1[0] = -inf (表示不可能) ans = 1 i=1 ( nums[1] = -2 ): dp0[1] = max(-2, 1 + (-2)) = max(-2, -1) = -1 dp1[1] = max(dp1[0] + (-2), dp0[0]) = max(-inf, 1) = 1 (这里对应删除当前元素 -2,保留前面的 1) ans = max(1, -1, 1) = 1 i=2 ( nums[2] = 0 ): dp0[2] = max(0, -1 + 0) = max(0, -1) = 0 dp1[2] = max(dp1[1] + 0, dp0[1]) = max(1 + 0, -1) = 1 ans = max(1, 0, 1) = 1 i=3 ( nums[3] = 3 ): dp0[3] = max(3, 0 + 3) = 3 dp1[3] = max(dp1[2] + 3, dp0[2]) = max(1 + 3, 0) = 4 ans = max(1, 3, 4) = 4 最终答案为 4,与例子一致。 代码实现(Python) 空间优化 由于 dp0[i] 和 dp1[i] 只依赖前一项,可以只用两个变量: 总结 这个问题的关键是将“允许删除一个元素”转化为两种状态:是否已经删除过。通过分别维护“未删除”和“已删除”的最大子段和,我们可以在线性时间内解决问题。此方法清晰且高效,是处理此类变种问题的典型思路。