力扣第 53 题「 最大子序和 」问题和前文讲过的 前提概要 的套路非常相似,代表着一类比较特殊的动态规划问题的思路,题目如下:

给你输入一个整数数组 nums,请你找在其中找一个和最大的子数组,返回这个子数组的和。函数签名如下:

int maxSubArray(int[] nums);

比如说输入 nums = [-3,1,3,-1,2,-4,2],算法返回 5,因为最大子数组 [1,3,-1,2] 的和为 5。

最大子数组:

其实第一次看到这道题,我首先想到的是滑动窗口算法,因为我们前文说过嘛,滑动窗口算法就是专门处理子串/子数组问题的,这里不就是子数组问题么?

前文 滑动窗口题目 中讲过,想用滑动窗口算法,先问自己几个问题:

1、什么时候应该扩大窗口?

2、什么时候应该缩小窗口?

3、什么时候更新答案?

我之前认为这题用不了滑动窗口算法,因为我认为 nums 中包含负数,所以无法确定什么时候扩大和缩小窗口。但经读者评论的启发,发现这道题确实是可以用滑动窗口技巧解决的。

我们可以在窗口内元素之和大于等于 0 时扩大窗口,在窗口内元素之和小于 0 时缩小窗口,在每次移动窗口时更新答案。先直接看解法代码,待会儿再解释:

int maxSubArray(int[] nums) {
    int left = 0, right = 0;
    int windowSum = 0, maxSum = Integer.MIN_VALUE;
    while(right < nums.length){
        // 扩大窗口并更新窗口内的元素和
        windowSum += nums[right];
        right++;
 
        // 更新答案
        maxSum = windowSum > maxSum ? windowSum : maxSum;
 
        // 判断窗口是否要收缩
        while(windowSum < 0) {
            // 缩小窗口并更新窗口内的元素和
            windowSum -=  nums[left];
            left++;
        }
    }
    return maxSum;
}

结合前文滑动窗口题目 给出的滑动窗口代码框架,这段代码的结构应该很清晰,我主要解释一下为什么这个逻辑是正确的。

首先讨论一种特殊情况,就是 nums 中全是负数的时候,此时算法是可以得到正确答案的。

接下来讨论一般情况,nums 中有正有负,这种情况下元素和最大的那个子数组一定是以正数开头的(以负数开头的话,把这个负数去掉,就可以得到和更大的子数组了,与假设相矛盾)。那么此时我们需要穷举所有以正数开头的子数组,计算他们的元素和,找到元素和最大的那个子数组。

说到这里,解法代码的逻辑应该就清晰了。算法只有在窗口元素和大于 0 时才会不断扩大窗口,并且在扩大窗口时更新答案,这其实就是在穷举所有正数开头的子数组,寻找子数组和最大的那个,所以这段代码能够得到正确的结果。