lc.53 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
kadane算法 >>求最大和 可延伸最小和等变种
- DP
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int target=nums[0];
int sum = 0;
for(int i=0;i<nums.size();i++)
{
if(sum>0)
{
sum += nums[i];
}
else
{
sum = nums[i];
}
target = max(target,sum);
}
return target;
}
};
一个月前做过,又给整不会了,mad
- DP 状态转移方程
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int target=nums[0];
int sum = 0;
for(int i=0;i<nums.size();i++)
{
sum=nums[i]+max(sum,0);
target = max(target,sum);
}
return target;
}
};
kadane的状态转移方程
- 线段树
class Solution {
public:
struct Status {
int lSum, rSum, mSum, iSum;
};
Status pushUp(Status l, Status r) {
int iSum = l.iSum + r.iSum;
int lSum = max(l.lSum, l.iSum + r.lSum);
int rSum = max(r.rSum, r.iSum + l.rSum);
int mSum = max(max(l.mSum, r.mSum), l.rSum + r.lSum);//max括号里只能有两个值,多个需要嵌套
return (Status) {lSum, rSum, mSum, iSum};
};
Status get(vector<int> &a, int l, int r) {
if (l == r) {
return (Status) {a[l], a[l], a[l], a[l]};
}
int m = (l + r) >> 1;
Status lSub = get(a, l, m);
Status rSub = get(a, m + 1, r);
return pushUp(lSub, rSub);
}
int maxSubArray(vector<int>& nums) {
return get(nums, 0, nums.size() - 1).mSum;
}
};
lc.918 环形子数组的最大和
给定一个由整数数组 A 表示的环形数组 C,求 C 的非空子数组的最大可能和。
在此处,环形数组意味着数组的末端将会与开头相连呈环状。(形式上,当0 <= i < A.length 时 C[i] = A[i],且当 i >= 0 时 C[i+A.length] = C[i])
此外,子数组最多只能包含固定缓冲区 A 中的每个元素一次。(形式上,对于子数组 C[i], C[i+1], …, C[j],不存在 i <= k1, k2 <= j 其中 k1 % A.length = k2 % A.length)
- 超时
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int c[2*nums.size()];
for(int i=0;i<nums.size();i++)
{
c[i]=nums[i];
c[nums.size()+i]=nums[i];
}
int sum=0;
int target=c[0];
for(int j=0;j<nums.size();j++){
for(int i=j;i<j+nums.size();i++)
{
if(sum<0)
{
sum=c[i];
}
else
{
sum += c[i];
}
target=max(target,sum);
}
sum=0;
}
return target;
}
};
直接在上一问基础上改动,n^2超时,意料之中,情理之中
- 邻接数组
单区间情况还是kadane解决,双区间选择分别计算,左侧到i结束,求和;右侧从i+2开始,查找最大和,由于要包含最后一个数,所以反向查找。
Java实现
class Solution {
public int maxSubarraySumCircular(int[] A) {
int N = A.length;
int ans = A[0], cur = A[0];
for (int i = 1; i < N; ++i) {
cur = A[i] + Math.max(cur, 0);
ans = Math.max(ans, cur);
}//中间情况,kadane
// ans is the answer for 1-interval subarrays.
// Now, let's consider all 2-interval subarrays.
// For each i, we want to know
// the maximum of sum(A[j:]) with j >= i+2
// rightsums[i] = A[i] + A[i+1] + ... + A[N-1]
int[] rightsums = new int[N];
rightsums[N-1] = A[N-1];
for (int i = N-2; i >= 0; --i)
rightsums[i] = rightsums[i+1] + A[i];
// maxright[i] = max_{j >= i} rightsums[j]
int[] maxright = new int[N];
maxright[N-1] = A[N-1];
for (int i = N-2; i >= 0; --i)
maxright[i] = Math.max(maxright[i+1], rightsums[i]);
int leftsum = 0;
for (int i = 0; i < N-2; ++i) {
leftsum += A[i];
ans = Math.max(ans, leftsum + maxright[i+2]);
}
return ans;
}
}
一开始不清楚为什么左侧要求和,实际上相当于从数列第一项开始依次作为新数列末尾,剩余部分从尾部开始查找最大和。
- 前缀和 + 单调队列
和我一开始想的一样,两个nums拼成一个定长数列解决。
Java实现,细节繁琐,实在牛批
class Solution {
public int maxSubarraySumCircular(int[] A) {
int N = A.length;
// Compute P[j] = B[0] + B[1] + ... + B[j-1]
// for fixed array B = A+A
int[] P = new int[2*N+1];
for (int i = 0; i < 2*N; ++i)
P[i+1] = P[i] + A[i % N];
// Want largest P[j] - P[i] with 1 <= j-i <= N
// For each j, want smallest P[i] with i >= j-N
int ans = A[0];
// deque: i's, increasing by P[i]
Deque<Integer> deque = new ArrayDeque();
deque.offer(0);
for (int j = 1; j <= 2*N; ++j) {
// If the smallest i is too small, remove it.
if (deque.peekFirst() < j-N)
deque.pollFirst();
// The optimal i is deque[0], for cand. answer P[j] - P[i].
ans = Math.max(ans, P[j] - P[deque.peekFirst()]);
// Remove any i1's with P[i2] <= P[i1].
while (!deque.isEmpty() && P[j] <= P[deque.peekLast()])
deque.pollLast();
deque.offerLast(j);
}
return ans;
}
}
- 单区间:kadane 双区间:sum - kadane (min变种)
很简便的一种方法,两次kadane。但是注意到,当全员负数时sum-minv==0
,最大子序和等于数组中最大的那个 maxv
。
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int sum=nums[0];
vector<int>dpmax(nums);
vector<int>dpmin(nums);
for(int i=1;i<nums.size();i++){
dpmax[i]=max(dpmax[i-1]+nums[i],nums[i]);
dpmin[i]=min(dpmin[i-1]+nums[i],nums[i]);
sum+=nums[i];
}
int maxv=*max_element(dpmax.begin(),dpmax.end());
int minv=*min_element(dpmin.begin(),dpmin.end());
return max(maxv,sum-minv==0?maxv:sum-minv);
}
};
max_element 与 min_element 都是 algorithm 库里的库函数,分别返回范围内最大与最小值。注意到 *max_element 与 *min_element。
- 双区间:kadane 符号变种
Java实现
class Solution {
public int maxSubarraySumCircular(int[] A) {
int S = 0; // S = sum(A)
for (int x: A)
S += x;
int ans1 = kadane(A, 0, A.length-1, 1);
int ans2 = S + kadane(A, 1, A.length-1, -1);
int ans3 = S + kadane(A, 0, A.length-2, -1);
return Math.max(ans1, Math.max(ans2, ans3));
}
public int kadane(int[] A, int i, int j, int sign) {
// The maximum non-empty subarray for array
// [sign * A[i], sign * A[i+1], ..., sign * A[j]]
int ans = Integer.MIN_VALUE;
int cur = Integer.MIN_VALUE;
for (int k = i; k <= j; ++k) {
cur = sign * A[k] + Math.max(cur, 0);
ans = Math.max(ans, cur);
}
return ans;
}
}