20.环形子数组的最大和(medium)

1.题目链接:

918. 环形子数组的最大和 - 力扣(LeetCode)918. 环形子数组的最大和 - 给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i + 1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i + 1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。 示例 1:输入:nums = [1,-2,3,-2]输出:3解释:从子数组 [3] 得到最大和 3示例 2:输入:nums = [5,-3,5]输出:10解释:从子数组 [5,5] 得到最大和 5 + 5 = 10示例 3:输入:nums = [3,-2,2,-3]输出:3解释:从子数组 [3] 和 [3,-2,2] 都可以得到最大和 3 提示: * n == nums.length * 1 <= n <= 3 * 104 * -3 * 104 <= nums[i] <= 3 * 104https://leetcode.cn/problems/maximum-sum-circular-subarray/description/2.题目描述:

给定一个长度为 n 的环形整数数组 nums ,返回 nums 的非空 子数组 的最大可能和 。​
    环形数组 意味着数组的末端将会与开头相连呈环状。形式上, nums[i] 的下一个元素是 nums[(i +     1) % n] , nums[i] 的前一个元素是 nums[(i - 1 + n) % n] 。​
    子数组 最多只能包含固定缓冲区 nums 中的每个元素一次。形式上,对于子数组 nums[i], nums[i +          1], ..., nums[j] ,不存在 i <= k1, k2 <= j 其中 k1 % n == k2 % n 。​
    示例 1:​
               输入:nums = [1,-2,3,-2]​
               输出:3​
    解释:

           从子数组 [3] 得到最大和 3​
示例 2:​
           输入:nums = [5,-3,5]​
           输出:10​
解释:

               从子数组 [5,5] 得到最大和 5 + 5 = 10​
3. 解法(动态规划):

算法思路:
        本题与「最大子数组和」的区别在于,考虑问题的时候不仅要分析「数组内的连续区域」,还要考   虑「数组首尾相连」的一部分。结果的可能情况分为以下两种:
                 i.     结果在数组的内部,包括整个数组;
                 ii. 结果在数组首尾相连的一部分上。

其中,对于第一种情况,我们仅需按照「最大子数组和」的求法就可以得到结果,记为 fmax 。​对于第二种情况,我们可以分析一下:

i.

如果数组首尾相连的一部分是最大的数组和,那么数组中间就会空出来一部分;

  ii. 因为数组的总和 sum  是不变的,那么中间连续的一部分的和一定是最小的;​因此,我们就可以得出一个结论,对于第二种情况的最大和,应该等于 sum - gmin ,其中 gmin  表示数组内的「最小子数组和」。​
两种情况下的最大值,就是我们要的结果。

但是,由于数组内有可能全部都是负数,第一种情况下的结果是数组内的最大值(是个负数),第二种情况下的 gmin == sum ,求的得结果就会是 0 。若直接求两者的最大值,就会是 0 。但是实际的结果应该是数组内的最大值。对于这种情况,我们需要特殊判断一下。

        由于「最大子数组和」的方法已经讲过,这里只提一下「最小子数组和」的求解过程,其实与「最       大子数组和」的求法是一致的。用 f  表示最大和,g  表示最小和。​
1. 状态表示:
          g[i]  表示:以 i  做结尾的「所有子数组」中和的最小值。​

2. 状态转移方程:
          g[i]  的所有可能可以分为以下两种:​

i.

子数组的长度为 1 :此时 g[i] = nums[i] ;​

  ii. 子数组的长度大于 1 :此时 g[i]  应该等于 以 i - 1  做结尾的「所有子数组」中和的            最小值再加上 nums[i] ,也就是 g[i - 1] + nums[i] 。​
由于我们要的是最小子数组和,因此应该是两种情况下的最小值,因此可得转移方程:
g[i] = min(nums[i], g[i - 1] + nums[i])

3. 初始化:
        可以在最前面加上一个辅助结点,帮助我们初始化。使用这种技巧要注意两个点:      i.     辅助结点里面的值要保证后续填表是正确的;

ii. 下标的映射关系。

在本题中,最前面加上一个格子,并且让 g[0] = 0  即可。​

4. 填表顺序:

根据状态转移方程易得,填表顺序为「从左往右」。

5. 返回值:

a. 先找到 f  表里面的最大值 -> fmax ;​

b. 找到 g  表里面的最小值 -> gmin ;​

c. 统计所有元素的和 -> sum ;​

b. 返回 sum == gmin ? fmax : max(fmax, sum - gmin) 。​

Java算法代码:

class Solution {
    public int maxSubarraySumCircular(int[] nums) {
        // 1.创建dp表
        // 2.初始化
        // 3.填表
        // 4.返回值
        int n = nums.length;
        int[] f = new int[n+1];
        int[] g = new int[n+1];
        int sum = 0,fmax= Integer.MIN_VALUE,gmin=Integer.MAX_VALUE;
        for(int i = 1; i <= n; i++){
            int x = nums[i-1];
            f[i] = Math.max(x,x + f[i-1]);
            fmax = Math.max(fmax,f[i]);
            g[i] = Math.min(x,x + g[i-1]);
            gmin = Math.min(gmin,g[i]);
            sum +=x;
        }
        return sum == gmin ? fmax : Math.max(fmax,sum - gmin);
    }
}

执行结果:

动态规划:

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值