环形数组最大子数组和问题解析 | 豆包MarsCode AI刷题

99 阅读7分钟

题目解析

问题描述 给定一个长度为 nn 的环形整数数组 nums,需要找到该数组中的非空子数组的最大可能和。环形数组的特点是它的末端和开头相连,形式上,nums[i] 的下一个元素是 nums[(i + 1) % n],而 nums[i] 的前一个元素是 nums[(i - 1 + n) % n]

示例

  • 输入:nums = [-1, -2, 3, -2],输出:3
  • 输入:nums = [-5, -3, 5],输出:5
  • 输入:nums = [-3, -1, 2, -1],输出:2
  • 输入:nums = [-2, -3, -1],输出:-1

思路

  1. 线性子数组的最大和:使用 Kadane's Algorithm 找到线性数组的最大子数组和。
  2. 跨越数组末端的最大和:计算整个数组的总和,然后减去最小的子数组和(使用 Kadane's Algorithm 的变种找到最小子数组和)。这样可以得到跨越数组末端的最大子数组和。
  3. 特殊情况处理:如果所有元素都是负数,那么跨越数组末端的最大和会变成 0,这不符合题意,因此在这种情况下,我们直接返回线性子数组的最大和。

图解 假设 nums = [-1, -2, 3, -2]

  1. 线性子数组的最大和

    • 使用 Kadane's Algorithm,从左到右遍历数组,找到最大子数组和。
    • [-1, -2, 3, -2] 的最大子数组和为 3
  2. 跨越数组末端的最大和

    • 计算整个数组的总和:-1 + (-2) + 3 + (-2) = -2
    • 使用 Kadane's Algorithm 的变种找到最小子数组和:-2
    • 跨越数组末端的最大和为 总和 - 最小子数组和 = -2 - (-2) = 0
    • 由于所有元素都是负数,返回线性子数组的最大和 3

代码详解

python

def kadane_max_subarray_sum(nums):
    max_current = max_global = nums[0]
    for num in nums[1:]:
        max_current = max(num, max_current + num)
        max_global = max(max_global, max_current)
    return max_global

def kadane_min_subarray_sum(nums):
    min_current = min_global = nums[0]
    for num in nums[1:]:
        min_current = min(num, min_current + num)
        min_global = min(min_global, min_current)
    return min_global

def solution(nums: list) -> int:
    max_linear_sum = kadane_max_subarray_sum(nums)
    total_sum = sum(nums)
    min_subarray_sum = kadane_min_subarray_sum(nums)
    
    # Handle the case where all elements are negative
    if min_subarray_sum == total_sum:
        return max_linear_sum
    
    max_circular_sum = total_sum - min_subarray_sum
    return max(max_linear_sum, max_circular_sum)

if __name__ == '__main__':
    print(solution([-1, -2, 3, -2]) == 3)  # 应该为 True
    print(solution([-5, -3, 5]) == 5)      # 应该为 True
    print(solution([-3, -1, 2, -1]) == 2)  # 应该为 True
    print(solution([-2, -3, -1]) == -1)    # 应该为 True

知识点具体解释

1. Kadane's Algorithm

定义与原理 Kadane's Algorithm 是一种用于找到线性数组中最大子数组和的经典算法。它的时间复杂度为 O(n),非常高效。算法的核心思想是通过动态规划的思想,维护一个当前子数组的最大和,不断更新全局最大和。

步骤

  1. 初始化

    • max_current:当前子数组的最大和,初始值为数组的第一个元素。
    • max_global:全局最大和,初始值为数组的第一个元素。
  2. 遍历数组

    • 对于数组中的每一个元素 num,更新 max_current 为 max(num, max_current + num),即当前元素和当前子数组的最大和之间的较大值。
    • 更新 max_global 为 max(max_global, max_current),即全局最大和和当前子数组的最大和之间的较大值。
  3. 返回结果

    • 返回 max_global,即全局最大和。

代码实现

python

def kadane_max_subarray_sum(nums):
    max_current = max_global = nums[0]
    for num in nums[1:]:
        max_current = max(num, max_current + num)
        max_global = max(max_global, max_current)
    return max_global

示例 假设 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

  • 初始化:max_current = -2max_global = -2

  • 遍历数组:

    • num = 1max_current = max(1, -2 + 1) = 1max_global = max(-2, 1) = 1
    • num = -3max_current = max(-3, 1 - 3) = -2max_global = max(1, -2) = 1
    • num = 4max_current = max(4, -2 + 4) = 4max_global = max(1, 4) = 4
    • num = -1max_current = max(-1, 4 - 1) = 3max_global = max(4, 3) = 4
    • num = 2max_current = max(2, 3 + 2) = 5max_global = max(4, 5) = 5
    • num = 1max_current = max(1, 5 + 1) = 6max_global = max(5, 6) = 6
    • num = -5max_current = max(-5, 6 - 5) = 1max_global = max(6, 1) = 6
    • num = 4max_current = max(4, 1 + 4) = 5max_global = max(6, 5) = 6
  • 返回结果:max_global = 6

2. Kadane's Algorithm 的变种

定义与原理 Kadane's Algorithm 的变种用于找到线性数组中的最小子数组和。其基本思路与原版相似,只是将最大值的比较改为最小值的比较。

步骤

  1. 初始化

    • min_current:当前子数组的最小和,初始值为数组的第一个元素。
    • min_global:全局最小和,初始值为数组的第一个元素。
  2. 遍历数组

    • 对于数组中的每一个元素 num,更新 min_current 为 min(num, min_current + num),即当前元素和当前子数组的最小和之间的较小值。
    • 更新 min_global 为 min(min_global, min_current),即全局最小和和当前子数组的最小和之间的较小值。
  3. 返回结果

    • 返回 min_global,即全局最小和。

代码实现

python

def kadane_min_subarray_sum(nums):
    min_current = min_global = nums[0]
    for num in nums[1:]:
        min_current = min(num, min_current + num)
        min_global = min(min_global, min_current)
    return min_global

示例 假设 nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]

  • 初始化:min_current = -2min_global = -2

  • 遍历数组:

    • num = 1min_current = min(1, -2 + 1) = -1min_global = min(-2, -1) = -2
    • num = -3min_current = min(-3, -1 - 3) = -4min_global = min(-2, -4) = -4
    • num = 4min_current = min(4, -4 + 4) = 0min_global = min(-4, 0) = -4
    • num = -1min_current = min(-1, 0 - 1) = -1min_global = min(-4, -1) = -4
    • num = 2min_current = min(2, -1 + 2) = 1min_global = min(-4, 1) = -4
    • num = 1min_current = min(1, 1 + 1) = 1min_global = min(-4, 1) = -4
    • num = -5min_current = min(-5, 1 - 5) = -4min_global = min(-4, -4) = -4
    • num = 4min_current = min(4, -4 + 4) = 0min_global = min(-4, 0) = -4
  • 返回结果:min_global = -4

3. 特殊情况处理

定义与原理 在处理环形数组的最大子数组和问题时,需要特别处理所有元素都是负数的情况。如果所有元素都是负数,那么跨越数组末端的最大和会变成 0,这不符合题意,因此在这种情况下,我们直接返回线性子数组的最大和。

步骤

  1. 计算线性子数组的最大和:使用 Kadane's Algorithm 找到线性数组的最大子数组和。

  2. 计算整个数组的总和:计算数组中所有元素的总和。

  3. 计算最小子数组和:使用 Kadane's Algorithm 的变种找到线性数组的最小子数组和。

  4. 处理特殊情况

    • 如果最小子数组和等于整个数组的总和,说明所有元素都是负数,直接返回线性子数组的最大和。
  5. 计算跨越数组末端的最大和:用整个数组的总和减去最小子数组和,得到跨越数组末端的最大子数组和。

  6. 返回结果:返回线性子数组的最大和和跨越数组末端的最大和中的较大值。

代码实现

python

def solution(nums: list) -> int:
    max_linear_sum = kadane_max_subarray_sum(nums)
    total_sum = sum(nums)
    min_subarray_sum = kadane_min_subarray_sum(nums)
    
    # Handle the case where all elements are negative
    if min_subarray_sum == total_sum:
        return max_linear_sum
    
    max_circular_sum = total_sum - min_subarray_sum
    return max(max_linear_sum, max_circular_sum)

示例 假设 nums = [-1, -2, 3, -2]

  • 计算线性子数组的最大和:max_linear_sum = 3
  • 计算整个数组的总和:total_sum = -1 + (-2) + 3 + (-2) = -2
  • 计算最小子数组和:min_subarray_sum = -2
  • 处理特殊情况:min_subarray_sum != total_sum
  • 计算跨越数组末端的最大和:max_circular_sum = total_sum - min_subarray_sum = -2 - (-2) = 0
  • 返回结果:max(max_linear_sum, max_circular_sum) = max(3, 0) = 3

可能出现的问题及解决办法

  1. 所有元素都是负数:如果所有元素都是负数,min_subarray_sum 会等于 total_sum,导致 max_circular_sum 为 0。这种情况下,直接返回 max_linear_sum
  2. 数组为空:在实际应用中,需要处理数组为空的情况,返回适当的错误信息或默认值。
  3. 边界条件:在实现 Kadane's Algorithm 时,需要注意边界条件,确保在数组只有一个元素时也能正确处理。

总结 通过这道题目,我学会了如何使用 Kadane's Algorithm 及其变种来解决最大子数组和问题,并且掌握了处理环形数组的方法。希望这些经验和方法能帮助其他同学更好地理解和解决类似问题。