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算法 结果即可。

相关推荐
小吴先生6667 分钟前
Groovy 规则执行器,加载到缓存
java·开发语言·缓存·groovy
小竹子148 分钟前
L1-1 天梯赛座位分配
数据结构·c++·算法
星星不打輰17 分钟前
Spring基于注解进行开发
java·spring
董董灿是个攻城狮18 分钟前
Transformer 通关秘籍8:词向量如何表示近义词?
算法
陈大爷(有低保)19 分钟前
Spring中都用到了哪些设计模式
java·后端·spring
骑牛小道士25 分钟前
JAVA- 锁机制介绍 进程锁
java·开发语言
高林雨露29 分钟前
Java对比学习Kotlin的详细指南(一)
java·学习·kotlin
独好紫罗兰40 分钟前
洛谷题单2-P5712 【深基3.例4】Apples-python-流程图重构
开发语言·python·算法
uhakadotcom1 小时前
NVIDIA Resiliency Extension(NVRx)简介:提高PyTorch训练的容错性
算法·面试·github
雷渊1 小时前
深入分析mybatis中#{}和${}的区别
java·后端·面试