题目描述:给定一个数组arr,返回子数组的最大累加和
例:arr=[1,-2,3,5,-2,6,-1];所有的子数组中[3,5,-2,6]可以累加出最大的和12,所以返回12
解题思路:
主要有两种选择:第一种就是最简单粗暴地挨个遍历,并且对其后面的连续子数组相加,然后不断更新最大累加和,当然这样的话效率比较低,是O(N²)的时间复杂度,这里就不介绍了。
第二种主要是通过看连续子数组的累加和对于最大累加和的贡献程度,此话怎讲呢?其实也就是当我们以某个元素为起点的时候,如果它后面的连续子数组的累加和为负的话,那么可以认定这个子数组对于最大累加和的贡献最小,我们可以弃用,直接将起点往后移动到子数组之后的一位再检验就行。
代码实现:
这里我主要用了一个哨兵和一个游标,哨兵作为每一次检验的起始点,而游标就向后推进,一旦累计和为负(贡献很小)我们就让哨兵到游标的位置去,并且让临时加和归零以便下次使用,否则就更新最大的累加和。这里同时可以记录一下最大累加和的区间,即每一次更新最大累加和的时候就记录这个区间的outset(开头)和end(末尾),这样一来就可以知道具体是哪一段子数组了。
//子数组最大累加和(要求是连续的子数组切片)
#include<iostream>
using namespace std;
int solve(int arr[],int N){
int sentry=0 , cursor=0 ; //定义哨兵和游标
int maxSum = arr[0]; //最大的和
int tmpSum = 0;
int outset; //子数组的开头
int end; //子数组的结尾
while(cursor < N ){
tmpSum += arr[cursor];
cursor ++;
if(tmpSum < 0){ //如果是负的,就说明是负贡献,应该调整哨兵
sentry = cursor;
tmpSum = 0; //归零
continue;
}
if(tmpSum > maxSum){
maxSum = tmpSum;
outset = sentry; //记录开头
end = cursor-1; //记录结尾
}
}
cout<<"区间是:["<<outset<<","<<end<<"]"<<endl;
return maxSum;
}
int main(){
int arr[] = {1,-2,3,5,-2,6,-1};
int res = solve(arr,7);
cout<<"result: "<<res;
}
时间复杂度分析:
这里比较好想,因为cursor一直没有回头地在推进,而且一定会走完整个区间,所以时间复杂度是O(N)。
总结:
这道题的解法存在一些瑕疵:最明显的就是如果原数组全是负数那么这种算法就不行了。
当然了~如果全是负数的话,那么最大累加和一定是最大的那个负数,因为负数加的越多就会越小,所以我们也可以在 if(tmpSum < 0) 这个条件里面也去更新最大累加和以及区间就行了。
就像这样:
if(tmpSum < 0){ //如果是负的,就说明是负贡献,应该调整哨兵
if(tmpSum > maxSum){
maxSum = tmpSum;
outset = sentry;
end = cursor-1;
}
sentry = cursor;
tmpSum = 0; //归零
continue;
}
实例:{-13,-22,-3,-5,-2,-6,-13,-14};
运行结果
大家想想如果这道题换成了矩阵,那应该怎么求子矩阵的最大累加和呢?可以看下我的这篇文章子矩阵的最大累加和