力扣53. 最大子数组和 + 918. 环形子数组的最大和

 

力扣53. 最大子数组和:【https://leetcode.cn/problems/maximum-subarray/description/

经典的dp + 空间优化,代码如下

 1 class Solution {
 2 public:
 3     int maxSubArray(vector<int>& nums) {
 4         int n = nums.size();
 5         // vector<int> dp(n);
 6         // dp[0] = nums[0];
 7         // int ret = dp[0];
 8         int dp = nums[0];
 9         int ret = dp;
10         for (int i = 1; i < n; ++i) {
11             // dp[i] = max(dp[i - 1] + nums[i], nums[i]);
12             dp = max(dp + nums[i], nums[i]);
13             ret = max(dp, ret);
14             // if (dp[i] > ret)
15                 // ret = dp[i];
16         }
17         return ret;
18     }
19 };

 

力扣918. 环形子数组的最大和:【https://leetcode.cn/problems/maximum-sum-circular-subarray/description/?envType=study-plan-v2&envId=top-interview-150

这个题目在基础题的基础上,增加了环形的可能性,想部分情况讨论,应该是解不出来的。

情况1还是按照基础题的来。

情况2是跨环形了,如果跨环形的话,意味着0到i这些数字必须都用到,j到n的数字也必须都用到,不能说按照常规dp那样,可以丢弃一部分。

所以先用lsum统计总数值,可能lsum包含了一些“累赘”,但是仍然都要用到,另外max中用的是dp[i - 1]和lsum作比较,也就意味着,

①假如前三个是【5,-3,5】,后面到i都是负数,那么此时dp[i]记录的仍然是前三个数的和,一直递推到最后,注意此时无法优化空间,因为反向递推的适合,需要用到之前的数据。

后面从右向左递推,rsum统计总数值,这时候rsum + dp2[i - 1]如果大于之前的最大值,则rsum + dp2[i - 1]就是最优解,这里之所以从右开始加,加到i的位置的总数加上dp2的[i - 1]就是最值的原因如①的描述。

 1 class Solution {
 2 public:
 3     int maxSubarraySumCircular(vector<int>& nums) {
 4         int n = nums.size();
 5         vector<int> dp2(n);
 6         dp2[0] = nums[0];
 7         int lsum = nums[0];
 8 
 9         int dp = nums[0];
10         int ret = dp;
11         for (int i = 1; i < n; ++i) {
12             dp = max(dp + nums[i], nums[i]);
13             ret = max(dp, ret);
14             lsum += nums[i];
15             dp2[i] = max(dp2[i - 1], lsum);
16         }
17 
18         int rsum = 0;
19         for (int i = n - 1; i > 0; --i) {
20             rsum += nums[i];
21             ret = max(ret, rsum + dp2[i - 1]);
22         }
23         return ret;
24     }
25 };

这个题目的dp方式是按照题解来的。

 

但是这个题目的最优解并不是dp,官方最终解法为,将情况二的正向思路改为反向思路,从而减少一轮遍历,从而让时间复杂度降低,最重要的是将空间复杂度降为常数。

思路是将情况二的【0到i】【j到n】的区间反向思维,求出连续最小值的和,然后用总的和减去这个数字,剩余的就是两端要求出来的最大和,由于最小和最大的求法一致,直接就减少了一轮遍历,并且空间都按照一个变量来算即可,减少了一个dp数组。

 1 class Solution {
 2 public:
 3     int maxSubarraySumCircular(vector<int>& nums) {
 4         int n = nums.size();
 5         int dp1 = nums[0], ret1 = dp1;
 6         int dp2 = nums[0], ret2 = dp2;
 7         int s = nums[0];
 8 
 9         for (int i = 1; i < n; ++i) {
10             dp1 = max(dp1 + nums[i], nums[i]);
11             ret1 = max(ret1, dp1);
12             dp2 = min(dp2 + nums[i], nums[i]);
13             ret2 = min(ret2, dp2);
14             s += nums[i];
15         }
16         return s == ret2 ? ret1 : max(ret1, s - ret2);
17     }
18 };

只能说反向思维,无敌!

 

posted @ 2025-06-03 16:11  J&YANG  阅读(17)  评论(0)    收藏  举报