【力扣每日一题】2023.7.20 环形子数组的最大和

文章讨论了一个编程问题,寻找一个环形数组中连续子数组的最大和。解决方案包括使用动态规划,考虑两种情况:子数组在原数组中间和子数组跨原数组首尾。对于全负数的情况,返回最小的负数。最终代码包括两种方法,一种超时,一种基于动态规划。
摘要由CSDN通过智能技术生成

题目:

示例:

分析:

题目描述的有点复杂,特别是那个公式,一看到我就烦.

其实意思就是找出一个子数组(连续),要求这个子数组的和是所有子数组中最大的,并且这个数组是环形的,意思就是我这个子数组可以从原数组的尾部开始,到原数组的头部结束,就是原数组的首尾是连同的.

那么我第一反应就是将原数组后面再连接一个原数组得到新数组,例如这样: 

这样就是首尾相连了,并且只要我把子数组的长度控制在小于等于原数组,那么取到的子数组就是等于在原数组首尾相连的情况下取到的子数组(即不会重复取同一个元素).

这时候再取前缀和,获取最大的子数组和(长度要控制在小于等于原数组),将两个索引的前缀和相减就是两个索引之间的元素之和(不理解的同学再仔细理解理解):

 代码在下面,总之结果是超时,就是提供一种思路.(回头看看代码,发现时间复杂度确实有点高)

以下思路参考力扣外国站大佬,大佬的思路确实厉害:

LeetCode - The World's Leading Online Programming Learning Platform

我们可以发现要取首尾相连的数组的最大子数组和,其实一共就两种情况,一种是常规情况,子数组就在原数组的中间:

 另外一种情况是这样的,子数组的开头在原数组的末尾,结尾在子数组的开头:

 如果是第一种情况,我们就可以参考力扣第53题最大子数组和,那题和本题基本一致,只不过本题的数组是头尾相连的,但如果是第一种情况,就没有利用到首尾相连的这一特性,因此可以直接套用53题的解法(具体思路就不说了,直接看代码叭)

//https://leetcode.cn/problems/maximum-subarray/
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int>dp(nums.size(),nums[0]);
        int res=dp[0];
        for(int i=1;i<nums.size();i++){
            dp[i]=max(0,dp[i-1])+nums[i];
            res=max(res,dp[i]);
        }
        return res;
    }
};

那么我们就算是把第一种情况给解决掉了,此时就剩第二种情况,其实第二种情况我们可以发现和第一种情况也很想,都是在原数组的中间有这么一段连续子数组,那么它们这两段连续的子数组有什么联系呢?

我们可以发现在第一种情况中,在原数组中间的的这段连续子数组是所有子数组里和最大的子数组,也就是我们要求的.

而第二种情况中,在原数组中间的这段连续子数组是什么,我们乍一看还看不出来是什么,其实中间这段是所有子数组里和最小的子数组,我把一个首尾相连的数组里和最小的连续子数组删去,剩下的不就是我能凑到的和最大的连续子数组了?

可能不太好理解,我举一个比较极端的例子给大家看看:

 如上图所示,中间这段是连续子数组的和为-200,而整个数组的和为-196,如果我把中间的害群之马和最小的子数组删掉,那么得到的首尾相连的最大子数组的和不就是整个数组的和减去最小连续子数组的和等于 -196-(-200) =4 了?

那么我们就算是找到规律了,我们只需要按照上面53题的做法使用动态规划来分别获取在首尾不相连的情况下,最大的连续子数组和最小的连续子数组,再分别比较第一种情况和第二种情况即可.

假设为第一种情况,那么结果就是找出的最大连续子数组的和.假设是第二种情况,那么结果就是整个数组的和减去最小的连续子数组.在两种情况都假设完之后,返回结果最大的一种情况.

但其实我们忽略了另一种极端情况,那就是整个数组都是负数的情况.

在这种情况下,我们应该返回的是整个数组中最小的那个负数,但按照我们上面的推导,我们会返回0,因为我们求出来最大的连续子数组的和确实是整个数组中最小的负数,但是整个数组的和减去最小的连续子数组的和的结果却是0(最小的连续子数组就是整个数组,如果整个数组减去最小的连续子数组就相当于是我们一个元素都不取,但是这样是不符合题意的,我们至少需要取一个),比最大的连续子数组的和更大,因此在整个数组都是负数的情况下我们返回的是0.

我们只需要在返回结果的时候多一个判断就可以了:

return Max>0 ? max(Max,Sum-Min) : Max;

 在最大的连续子数组的和大于0的时候我们再比较两种情况谁更大,然后返回更大的一个数.

如果最大的连续子数组的和小于0,这时就是整个数组都是负数的情况了,我们则不用比较,直接返回最大的连续子数组的和就可以了.

代码+运行结果:

前缀和,但是超时,提供一种解题思路.

class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        //前缀和超时
        int n=nums.size();
        int res=nums[0];
        int temp=0;
        vector<int>newNums(2*n),tempNums(2*n);
        for(int i=0;i<2;i++){
            for(int j=0;j<n;j++) newNums[i*n+j]=nums[j];
        }
        for(int i=0;i<2*n;i++){
            tempNums[i]=temp;
            temp+=newNums[i];
        }
        for(int i=0;i<n;i++){
            for(int j=i+1;j<2*n && j-i<n+1;j++){
                res=max(res,tempNums[j]-tempNums[i]);
            }
        }
        return res;
    }
}
class Solution {
public:
    int maxSubarraySumCircular(vector<int>& nums) {
        //参考的https://leetcode.com/problems/maximum-sum-circular-subarray/solutions/178422/One-Pass/的动态规划
        int n=nums.size();
        vector<vector<int>>dp(2,vector<int>(n,nums[0]));
        int Max=nums[0],Min=nums[0],Sum=nums[0];
        for(int i=1;i<n;i++){
            Sum+=nums[i];
            dp[0][i]=max(0,dp[0][i-1])+nums[i];
            dp[1][i]=min(0,dp[1][i-1])+nums[i];
            Max=max(Max,dp[0][i]);
            Min=min(Min,dp[1][i]);
        }
        return Max>0 ? max(Max,Sum-Min) : Max;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值