uacs2024

导航

< 2025年2月 >
26 27 28 29 30 31 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 1
2 3 4 5 6 7 8

统计

leetcode 53. 最大子数组和

53. 最大子数组和

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

 

法一:

1.假如全是负数,那就是找最大值即可,因为负数肯定越加越大。

2.如果有正数,则肯定从正数开始计算和,不然前面有负值,和肯定变小了,所以从正数开始。

3.当和小于等于0时,这个区间就告一段落了,然后从下一个正数重新开始计算(也就是又回到 第2步 了)。而 dp 也就体现在这个地方。

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxSum = nums[0];
        int sum = 0,size = nums.size(); //当前最大连续子序列和为sum
        for(int i = 0;i < size;i++){
            if(sum > 0)  sum += nums[i];//如果sum>0,则说明sum对结果有增益效果,则sum保留并加上当前遍历数字
            else  sum = nums[i];//如果sum<=0,则说明sum对结果无增益效果,需要舍弃,则sum直接更新为当前遍历数字
            maxSum = max(maxSum , sum);//如果这一轮循环的nums[i]是负数,sum就可能变小了,所以需要和maxSum比大小
        }
        return maxSum;
    }
};
复制代码

法二:使用额外空间

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int maxRes = nums[0],size = nums.size();
        int dp[size];//dp[i]代表以第 i 个数结尾的「连续子数组的最大和」
        dp[0] = nums[0];
        for(int i = 1; i < size; i++){
            //不是dp[i]=max(dp[i-1],dp[i-1]+nums[i]);而是dp[i]=max(nums[i],dp[i-1]+nums[i]);
            dp[i] = max(nums[i], dp[i-1] + nums[i]);
            maxRes = max(maxRes, dp[i]);
        }
        return maxRes;
    }
};
复制代码

法三:递归分治法

复制代码
class Solution {
public:
    int max3(int num1,int num2,int num3){
        return max(num1,max(num2,num3));
    }

    int maxCrossingSum(vector<int>& nums, int left, int mid, int right){
        //考虑第 3 部分跨越两个区间的连续子数组的时候,由于 nums[mid] 与 nums[mid + 1] 一定会被选取
        //可以从中间向两边扩散,扩散到底选出最大值
        int sum = 0;
        int leftSum = INT_MIN;
        // 左半边包含 nums[mid] 元素,最多可以到什么地方
        // 走到最边界,看看最值是什么
        // 计算以 mid 结尾的最大的子数组的和
        for(int i = mid;i >= left;i--){
            sum += nums[i];
            if(sum > leftSum)  leftSum = sum;
        }
        sum = 0;
        int rightSum = INT_MIN;
        // 右半边不包含 nums[mid] 元素,最多可以到什么地方
        // 计算以 mid+1 开始的最大的子数组的和
        for (int i = mid + 1; i <= right; i++) {
            sum += nums[i];
            if (sum > rightSum)  rightSum = sum;          
        }
        return leftSum + rightSum;
    }

    int maxSubArraySum(vector<int>& nums,int left,int right){
        if(left == right)  return nums[left];
        int mid = left + (right - left) / 2;//可以很好地防止溢出
        //连续子序列的最大和主要由这三部分子区间里元素的最大和得到:
        //第 1 部分:子区间 [left, mid];
        //第 2 部分:子区间 [mid + 1, right];
        //第 3 部分:包含子区间 [mid , mid + 1] 的子区间,即 nums[mid] 与 nums[mid + 1] 一定会被选取。
        //对这三个部分求最大值即可。
        return max3(maxSubArraySum(nums, left, mid), maxSubArraySum(nums, mid+1, right), maxCrossingSum(nums, left, mid, right));
    }

    int maxSubArray(vector<int>& nums) {
        return maxSubArraySum(nums,0,nums.size()-1);
    }
};
复制代码

法四:前缀和

例如 nums=[1,2,1,2],对应的前缀和数组为 s=[0,1,3,4,6]。

通过前缀和,我们可以把子数组的元素和转换成两个前缀和的差

我们可以一边遍历数组计算前缀和,一边维护前缀和的最小值,用当前的前缀和减去前缀和的最小值,就得到了以当前元素结尾的子数组和的最大值,用它来更新答案的最大值

请注意,由于题目要求子数组不能为空,应当先计算前缀和-最小前缀和,再更新最小前缀和

如果先更新最小前缀和,再计算前缀和-最小前缀和,就会把空数组的元素和 0 算入答案。


示例的 前缀和-最小前缀和的最大值等于 6,即为答案。

复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        //这里使用INT_MIN作为res的初始值,是为了确保任何有效的子数组和都会大于这个初始值,从而能够被正确更新。
        int res = INT_MIN;
        // 初始化最小前缀和为0,用于跟踪到目前为止的最小前缀和
        int minPreSum = 0,
        // 初始化当前前缀和为0,用于计算到当前元素为止的数组和
        preSum = 0;
        
        for(int &n : nums){
            // 更新当前前缀和,加上当前元素的值
            preSum += n;
            // 更新最大子数组和,通过当前前缀和减去之前遇到的最小前缀和来找到可能的最大值
            // 这一步考虑了数组中包含负数的情况,通过从当前前缀和中减去最小前缀和来找到可能的最大子数组和
            res = max(res,preSum - minPreSum);
            // 更新最小前缀和,保持为到目前为止遇到的最小前缀和
            minPreSum = min(minPreSum,preSum);
        }
        
        return res;
    }
};
复制代码

 

posted on   ᶜʸᵃⁿ  阅读(45)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 【.NET】调用本地 Deepseek 模型
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 我与微信审核的“相爱相杀”看个人小程序副业
· DeepSeek “源神”启动!「GitHub 热点速览」
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示