Leetcode 环形子数组的最大和

解题思路

要解决这道题,我们需要考虑两种情况:

  1. 子数组的最大和不跨越数组两端(即标准的子数组最大和问题,可用Kadane算法解决)。
  2. 子数组的最大和跨越数组两端 。可以通过计算 总和 - 最小子数组和 来得到跨越部分的最大和。

最后,取 情况1和情况2中的最大值 作为结果,但需要注意特殊情况,当所有元素都是负数时,情况2的结果会错误。

java 复制代码
class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int curMax = 0;
        int globalMax = Integer.MIN_VALUE;
        int curMin = 0;
        int globalMin = Integer.MAX_VALUE;
        int totalSum = 0; //这几个初始值的设置很重要
        for(int num:nums) {
        // 最大子数组和 (Kadane's 算法)
            curMax = Math.max(num, curMax + num);
            globalMax = Math.max(globalMax, curMax);

            curMin = Math.min(num, curMin + num);
            globalMin = Math.min(curMin, globalMin);

            totalSum += num;
        }
        // 如果最大子数组和小于0(即数组中全为负数),直接返回maxSum
        if(globalMax < 0) {
            return globalMax;
        }
         // 否则,返回跨越和不跨越两种情况的最大值
        return Math.max(totalSum - globalMin, globalMax);
    }
}

为什么第二种情况时,可以通过计算 总和 - 最小子数组和 来得到跨越部分的最大和?

这是一个关键的理解点!让我们一步步解释为什么**总和 - 最小子数组和** 可以表示跨越两端的最大子数组和。


1. 环形子数组的本质

对于一个环形数组:

  • 子数组可以跨越数组两端 ,比如在 [3, -2, 2, -3] 中,子数组 [3, -3] 就是跨越了两端。
  • 因为数组是环形的,跨越部分的子数组 实际上是将数组中的两端去掉一个连续的中间部分

2. 如何描述跨越两端的子数组?

假设数组总和是 totalSum,数组中有一个连续的最小子数组和 ,我们称它为 minSubarraySum

那么:
  • 跨越两端的最大子数组和 可以看作是:
    • 总和 totalSum 减去中间的最小子数组和 minSubarraySum
为什么?
  • 因为将中间的最小子数组"剥离"掉,剩下的部分就是两端的最大和子数组

3. 数学直观解释

假设数组 nums 被分成三部分:

  • 左端 :从数组的起点到某个位置 i 的元素。
  • 右端 :从某个位置 j 到数组的终点的元素。
  • 中间部分 :从位置 i+1 到位置 j-1 的元素。

跨越两端的最大子数组和 就是:

\\text{左端} + \\text{右端} = \\text{总和} - \\text{中间部分}

而"中间部分"的最小和,就是数组的最小子数组和 minSubarraySum

所以我们有:

\\text{跨越两端的最大子数组和} = \\text{总和} - \\text{最小子数组和}


4. 特殊情况

当数组中的所有元素都为负数时:

  • 最小子数组和 就等于整个数组的和 totalSum
  • 此时 totalSum - minSubarraySum = 0,但实际的最大子数组和是负数,因此我们单独返回 Kadane 算法计算的 maxSum

5. 示例验证

举个例子,数组 nums = [5, -3, 5]

  1. 总和

    ( totalSum = 5 + (-3) + 5 = 7 )

  2. 最小子数组和

    最小子数组是 [-3],所以 ( minSubarraySum = -3 )。

  3. 跨越两端的最大和

    totalSum - minSubarraySum = 7 - (-3) = 10

  4. Kadane 算法计算的最大子数组和(不跨越)

    子数组 [5, -3, 5] 的和也是 10。

所以答案是 10


总结

通过**总和 - 最小子数组和**,我们有效地剥离出中间最小和的部分,剩下的就是跨越两端的最大和子数组。这种方法简单高效,且时间复杂度为 O(n)

也就是说,第二种情况时,总和 - 最小子数组和中的"最小子数组和"不能包含两端的数组元素

这是因为:

  1. 跨越两端的最大子数组和 是将数组的首尾部分连接起来,剥离掉中间的一段元素。
  2. 如果最小子数组和包含了首尾元素,就不可能真正剥离出"中间的连续子数组"。

为什么如果数组元素全部为负数时,不需要考虑情况2?

如果数组的元素全部为负数时,情况2(跨越两端的最大子数组和) 不再适用,原因如下:


1. 情况2的数学原理

跨越两端的最大子数组和 是通过:

\\text{总和} - \\text{最小子数组和}

来得到的,其中 最小子数组和 表示数组中和最小的连续子数组。

但当数组中的所有元素都是负数时:

  • 总和 是负数(因为所有元素相加必然小于等于0)。
  • 最小子数组和 就是整个数组本身的和(仍为负数)。

因此:

\\text{总和} - \\text{最小子数组和} = \\text{负数} - \\text{负数} = 0

这种情况下的结果 0 不是我们需要的最大子数组和。


2. 真实的最大子数组和

当数组中所有元素为负数时:

  • 最大子数组和一定是数组中的单个元素,即最大的负数(Kadane算法会正确返回这个值)。
  • 这是因为任何包含多个元素的子数组的和都会更小(更负)。

例如:

数组 nums = [-3, -2, -1, -4]

  1. Kadane算法(情况1)
    • 最大子数组和是 -1
  2. 情况2(跨越两端)
    • 总和 = -3 + (-2) + (-1) + (-4) = -10
    • 最小子数组和 = -10(整个数组)
    • (\text{总和} - \text{最小子数组和} = -10 - (-10) = 0)

0 并不合理,因为数组中的最大子数组和只能是 -1


3. 为什么直接返回 Kadane 算法的结果?

Kadane算法(情况1)在数组中所有元素为负数时:

  • 自然会选择最大的单个元素作为结果。
  • 不需要考虑情况2,因为跨越两端的子数组和永远会错误地趋近于0。

总结

当数组所有元素为负数时:

  1. 情况2不适用 ,因为 总和 - 最小子数组和 会导致不合理的 0 结果。
  2. Kadane算法(情况1) 会正确地找到最大的负数(即单个元素),这是符合预期的最大子数组和。

因此,在这种情况下,只需要考虑 Kadane算法 结果即可。

相关推荐
Derek_Smart3 小时前
从一次 OOM 事故说起:打造生产级的 JVM 健康检查组件
java·jvm·spring boot
NE_STOP4 小时前
MyBatis-mybatis入门与增删改查
java
孟陬7 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌7 小时前
一站式了解四种限流算法
java·后端·go
华仔啊8 小时前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
也些宝9 小时前
Java单例模式:饿汉、懒汉、DCL三种实现及最佳实践
java
Gorway9 小时前
解析残差网络 (ResNet)
算法
拖拉斯旋风9 小时前
LeetCode 经典算法题解析:优先队列与广度优先搜索的巧妙应用
算法
Wect9 小时前
LeetCode 207. 课程表:两种解法(BFS+DFS)详细解析
前端·算法·typescript