例如:
输入:1,-2,3,10,-4,7,2,-5
最大子数组和为18
一般而言,有三种办法可以用于解决这个问题
1.暴力破解法
暴力破解法就是将所有的子数组的和全部加起来,取最大的
算法最简单,但是时间复杂度最高
时间复杂度:O(n)=n^3
var sum=-1000;
for (var i=0;i<arr.length;i++) {
//遍历从i开始的所有子数组
for (var j=i;j<arr.length;j++) {
var thisSum=0;
//累计求和
for (var k=i;k<=j;k++) {
thisSum+=arr[k];
}
if (thisSum>sum) {
//记录最大子数组的位置和长度
sum=thisSum
}
}
}
console.log(sum);//sum=18
2.分治法
分治法的核心思想就是将大问题分解为小问题,再将小问题逐个解决,然后从小问题的解得到原问题的解
如果把数组从任一点(一般取中点)分为两个数组,那最大子数组只能存在于三个位置1.左数组
2.右数组
3.左数组最大后缀和右数组最大前缀的拼接(我称为中间数组)
然后把分得的两个数组使用递归算法继续分割,直到每个子数组只含有一个元素
此时两两进行判断:
若左数组较大,并返回左数组的值,右数组一样
若中间数组较大(此处即左右最大前缀和后缀的和,则返回这个和)
算法时间复杂度:O(n)=nlogn
function findMaxChildArr(arr,left,right){
if (left==right) {
//如果左等于右,则说明这个子数组只存在一个元素,则返回它用于比较
return arr[left];
}else{
//递归思想
var middle=parseInt((left+right)/2);
var leftMax=findMaxChildArr(arr,left,middle);//寻找左侧数组最大值;
var rightMax=findMaxChildArr(arr,middle+1,right);//寻找右侧数组最大值;
var middleMax=findMiddleMax(arr,left,right,middle);//寻找中间最大值
if (leftMax>=rightMax&&leftMax>=middleMax) {
return leftMax;
}else if(rightMax>=leftMax&&rightMax>middleMax){
return rightMax;
}else{
return middleMax;
}
}
}
function findMiddleMax(arr,left,right,middle){
var lmidMax=-1000;
var rmidMax=-1000;
var sum=0;
//找到向左的最大前缀
for (var i=middle;i>=left;i--) {
sum+=arr[i];
if (sum>lmidMax) {
lmidMax=sum;
}
}
//找到向右的最大后缀
sum=0;
for (var i=middle+1;i<right;i++) {
sum+=arr[i];
if (sum>rmidMax) {
rmidMax=sum;
}
}
return (lmidMax+rmidMax);
}
var max=findMaxChildArr(arr,0,arr.length);
console.log(max)//18
3.动态规划法
定义数组p,p[i]表示从arr[0]开始的前i项和,定义p[-1]=0,设arr=[1,-3,5,-2,4]
则有:
p[0]=max(p[-1]+arr[0],arr[0]) p[0]=max(0+1,1)=1;
p[1]=max(p[ 0]+arr[1],arr[1]) p[1]=max(1-3,-3)=-2;
p[2]=max(p[ 1]+arr[2],arr[2]) p[2]=max(-2+5,5)=5;
p[3]=max(p[ 2]+arr[3],arr[3]) p[3]=max(5-2,-2)=3;
p[4]=max(p[ 3]+arr[4],arr[4]) p[4]=max(3+4,4)=7;
所以该数组的最大子数组之和为7
即
p[i-1]+arr[i]>arr[i]?p[i]=p[i-1]+arr[i]:p[i]=arr[i];
进一步思考,能否只用一个变量来起到代替数组p的作用呢?答案是肯定的经过观察,我们发现取到arr[i]这个值的前提是,arr[i]需要给我们的最大子数组之和的这个和带来正面作用,也就是让子数组之和更大,这样的话我们才会取到这个arr[i]所以,我们可以用一个变量thisMax表示当前累加的和,如果它加上arr[i]之后比原来的max更大(也就是对max起正面作用),那我们就把这一项算入到最大子数组当中,另外,如果thisMax+arr[i]之后导致arr[i]比原先还要小,我们就可以理解为thisMax在求最大子数组之和这件事上没有发挥正面作用,所以我们以arr[i]为第一项重新开始累计最大子数组之和.
这样,只需要一个循环我们就可以解决问题
时间复杂度:O(n)=n
(动态规划是真滴强)
var max=0;
var thisMax=0;
for (var i=0;i<arr.length;i++) {
thisMax+=arr[i];
//若thisMax有没有令arr[i]变小,则从arr[i]重新开始累计
if (thisMax<arr[i]) thisMax=arr[i];
//若当前的thisMax已经比上一个max大,则将thisMax记为max
if (thisMax>max) max=thisMax;
}
console.log(max);