做环形数组最大和之前先做一下数组最大和
53.数组最大和
题目链接:LeetCode53.数组最大和
本体使用动态规划或者贪心
动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int>dp(nums.size(),0);
dp[0]=nums[0];
int ans=nums[0];
for(int i=1;i<nums.size();++i) {
int num = dp[i-1]+nums[i];
if(num>nums[i]) dp[i]=num;
else dp[i]=nums[i];
ans =ans>dp[i]?ans:dp[i];
}
return ans;
}
};
贪心
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum=nums[0];
int ans = nums[0];
for(int i=1;i<nums.size();++i){
sum+=nums[i];
if(sum<=nums[i]) sum=nums[i];
ans = ans>sum?ans:sum;
}
return ans;
}
};
918.环形数组的最大和
动态规划
求解普通数组的最大子数组和是求解环形数组的最大子数组和问题的子集。设数组长度为n,下标从0开始,在环形数组中,答案可能包括以下两种情况:
- 构成最大子数组和的子数组为nums[i:j],包括nums[i]到nums[j-1]共j-i个元素,其中0<i<j<=n
- 构成最大子数组和的子数组为nums[0:i]和nums[j:n]其中0<i<j<n
第二种情况中,答案可以分为两部分,nums[0:i]为数组的某一前缀,nums[j:n]为数组的某一后缀。求解时,可以枚举j,固定sum(nums[j:n])的值,然后找到右端点坐标范围在[0,j-1]的最大前缀和,将它们相加更新答案。
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int n = nums.size();
vector<int>leftmax(n,0);//记录数组的前缀最大子数组和,下标从0开始
leftmax[0]=nums[0];
int sum=nums[0];//用来记录普通数组的子数组的和
int res=nums[0];//普通数组的最大子数组和
int leftsum=nums[0];
for(int i=1;i<n;++i){
sum+=nums[i];
if(sum<=nums[i]) sum=nums[i];
res = res>sum?res:sum;
leftsum+=nums[i];
leftmax[i]=leftmax[i-1]>leftsum?leftmax[i-1]:leftsum;
}
if(res<0) return res;//数组中的元素都是负数
//固定后缀
int rightsum=0;
for(int i=n-1;i>0;--i){
rightsum+=nums[i];
res = max(res,leftmax[i-1]+rightsum);
}
return res;
}
};
取反
对于第二种情况,可以找到普通数组最小的子数组nums[i-j]。令maxRes是普通数组的最大子数组和,minRes是普通数组的最小子数组和,可以将maxRes与sum(nums[0-n])-minRes取最大值作为答案。
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int sum=nums[0];//求解数组的和
int summax=nums[0],maxres=nums[0];//最大子数组和
int summin=nums[0],minres=nums[0];//最小子数组和
for(int i=1;i<nums.size();++i){
summax = max(summax+nums[i],nums[i]);
maxres = max(maxres,summax);
summin = min(summin+nums[i],nums[i]);
minres = min(minres,summin);
sum+=nums[i];
}
//cout<<maxres<<" "<<minres<<" "<<sum;
if(maxres<0) return maxres;
return max(maxres,sum-minres);
}
};
单调队列
可以将数组延长一倍,即对于i>=n的元素,令nums[i]=nums[i-n]
对于第二种情况,nums[0:i]和nums[j:n]可以组成连续的一段,因此问题转换为一个在长度为2n的数组上寻找长度不超过n的最大子数组和(每个位置的元素只能使用一遍)。令si为前i项的前缀和,找到最大的si-sj其中i-n<=j<i,用单调队列维护该集合
- 遍历到i时,单调队列头部元素下标小于i-n,则出队。该过程一直进行,直至队列为空或者队头下标大于等于i-n
- 取队头元素作为j,计算si-sj更新答案
- 若队列尾部元素k满足sk>=si则出队,该过程一直进行,直至队列为空或者条件不满足因为k<i,k更容易被步骤1剔除,并且作为被减项,sk比si更大,更不具有优势。
class Solution {
public:
typedef pair<int,int> pa;//位置到前缀和的映射
int maxSubarraySumCircular(vector<int>& nums) {
int sum=nums[0],res=nums[0];
int n = nums.size();
deque<pa> qu;
qu.push_back(pa(0,sum));
for(int i=1;i<2*n;++i){
sum+=nums[i%n];
while(!qu.empty()&&qu.front().first<i-n) {
qu.pop_front();
}
res = max(res,sum-qu.front().second);
while(!qu.empty()&&qu.back().second>=sum){
qu.pop_back();
}
qu.push_back(pa(i,sum));
}
return res;
}
};