解题思路
要解决这道题,我们需要考虑两种情况:
- 子数组的最大和不跨越数组两端(即标准的子数组最大和问题,可用Kadane算法解决)。
- 子数组的最大和跨越数组两端 。可以通过计算 总和 - 最小子数组和 来得到跨越部分的最大和。
最后,取 情况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]
:
-
总和 :
( totalSum = 5 + (-3) + 5 = 7 )
-
最小子数组和 :
最小子数组是
[-3]
,所以 ( minSubarraySum = -3 )。 -
跨越两端的最大和 :
[
totalSum - minSubarraySum = 7 - (-3) = 10
]
-
Kadane 算法计算的最大子数组和(不跨越) :
子数组
[5, -3, 5]
的和也是 10。
所以答案是 10。
总结
通过**总和 - 最小子数组和
**,我们有效地剥离出中间最小和的部分,剩下的就是跨越两端的最大和子数组。这种方法简单高效,且时间复杂度为 O(n)。
也就是说,第二种情况时,总和 - 最小子数组和中的"最小子数组和"不能包含两端的数组元素
这是因为:
- 跨越两端的最大子数组和 是将数组的首尾部分连接起来,剥离掉中间的一段元素。
- 如果最小子数组和包含了首尾元素,就不可能真正剥离出"中间的连续子数组"。
为什么如果数组元素全部为负数时,不需要考虑情况2?
如果数组的元素全部为负数时,情况2(跨越两端的最大子数组和) 不再适用,原因如下:
1. 情况2的数学原理
跨越两端的最大子数组和 是通过:
[
\text{总和} - \text{最小子数组和}
]
来得到的,其中 最小子数组和 表示数组中和最小的连续子数组。
但当数组中的所有元素都是负数时:
- 总和 是负数(因为所有元素相加必然小于等于0)。
- 最小子数组和 就是整个数组本身的和(仍为负数)。
因此:
[
\text{总和} - \text{最小子数组和} = \text{负数} - \text{负数} = 0
]
这种情况下的结果 0
不是我们需要的最大子数组和。
2. 真实的最大子数组和
当数组中所有元素为负数时:
- 最大子数组和一定是数组中的单个元素,即最大的负数(Kadane算法会正确返回这个值)。
- 这是因为任何包含多个元素的子数组的和都会更小(更负)。
例如:
数组 nums = [-3, -2, -1, -4]
:
- Kadane算法(情况1) :
- 最大子数组和是
-1
。
- 最大子数组和是
- 情况2(跨越两端) :
总和 = -3 + (-2) + (-1) + (-4) = -10
最小子数组和 = -10
(整个数组)- (\text{总和} - \text{最小子数组和} = -10 - (-10) = 0)
但 0
并不合理,因为数组中的最大子数组和只能是 -1
。
3. 为什么直接返回 Kadane 算法的结果?
Kadane算法(情况1)在数组中所有元素为负数时:
- 自然会选择最大的单个元素作为结果。
- 不需要考虑情况2,因为跨越两端的子数组和永远会错误地趋近于0。
总结
当数组所有元素为负数时:
- 情况2不适用 ,因为
总和 - 最小子数组和
会导致不合理的0
结果。 - Kadane算法(情况1) 会正确地找到最大的负数(即单个元素),这是符合预期的最大子数组和。
因此,在这种情况下,只需要考虑 Kadane算法 结果即可。