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

相关推荐
兩尛几秒前
98. 验证二叉搜索树(java)
算法
看海的四叔几秒前
【Python】主成分分析PCA - 算法、问题与Python实现
人工智能·python·算法·机器学习·数据分析
有点困的拿铁44 分钟前
Spring篇--xml方式整合第三方框架
xml·java·spring
武子康1 小时前
Java-30 深入浅出 Spring - IoC 基础 启动IoC 纯XML启动 Bean、DI注入
xml·java·开发语言·后端·spring·mybatis·springboot
不烦下雨c1 小时前
【优选算法---前缀和】和为K的子数组、和可被K整除的子数组、连续数组、矩阵区域和
java·数据结构·算法
东辰芯力1 小时前
探索未来物联网开发——HiSpark平台与海思IDE安装指南
人工智能·单片机·嵌入式硬件·算法·risc-v
Q_19284999061 小时前
基于Spring Boot的数码产品抢购系统
java·spring boot·后端
小娄写码1 小时前
Java设计模式及示例
java·开发语言·设计模式
路在脚下@1 小时前
spring boot密码加密方式
java·开发语言