与动态规划的爱恨情仇——环形子数组的最大和

82 阅读2分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第11天,点击查看活动详情


最近一直在力扣刷题,也逐渐对各类题型有了自己的理解,所谓见招拆招,将自己的浅显经验分享一下,帮助更多在编程路上的朋友们。


环形子数组的最大和

给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。

环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。  

示例 1:

输入:nums = [1,-2,3,-2]

输出:3

解释:从子数组 [3] 得到最大和 3

示例 2:

输入:nums = [5,-3,5]

输出:10

解释:从子数组 [5,5] 得到最大和 5 + 5 = 10

思路

子数组要求是数组连续的,不能改变数组的顺序,很容易想到使用动态规划,如果想不到,建议养成肌肉记忆T_T

动态规划方程很简单: dp[i] = Math.max(dp[i - 1] + nums[i], nums[i])

但是dp[n - 1]并不是最大的子数组,例如dp[n - 2] = 6, nums[n - 1] = -1,此时的dp[n - 1] = 5,故需要一个max变量存储子数组的最大值。

状态转移方程如下

for(int i = 1; i < n; i++) {
    dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
    max = Math.max(dp[i], max);
}

以上为一般数组求子数组最大和方法,但该题要求环形数组的子数组,需要考虑到数组末尾与首部的关系,就算循环两次,也无法确定会不会出现首尾重复计算的情况,这个问题也困扰了我很久,最后还是要靠大神的解法解决,分享如下

image.png

分为两种情况,一种最大子数组首尾未成环,上文已讲述,如果最大子数组成环,则最大子数组的和等价于数组和减去最小子数组的和

还有一种情况需要考虑,如果数组元素全为负数时,最小子数组和就会等于数组和,此时sum - min == 0,故最后需要判断若最大值为负数时,改最大值即为子数组最大和。

题解

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        int n = nums.length;
        int[] dp = new int[n];
        dp[0] = nums[0];
        int max = nums[0];
        int sum = nums[0];
        for(int i = 1; i < n; i++) {
            sum += nums[i];
            dp[i] = Math.max(dp[i - 1] + nums[i], nums[i]);
            max = Math.max(max, dp[i]);
        }
        int min = nums[0];
        for(int i = 1; i < n; i++) {
            dp[i] = Math.min(dp[i - 1] + nums[i], nums[i]);
            min = Math.min(min, dp[i]);
        }
        return max < 0? max: Math.max(max, sum - min);
    }
}