编程之美——求数组的子数组之和的最大值(一维)

编程之美——求数组的子数组之和的最大值(一维)


问题:一个有N个整数元素的一维数组(A[0]、A[1],…A[n-1]),求子数组之和的最大值。

方法一:蛮力法 时间复杂度为O(N^2).
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum(int *A,int n)
{
  int maximum=INT_MIN;
  int sum;
  for(int i=0;i<n;i++)
  {
    int sum=0;
for(int j=i;j<n;j++)
{
 sum+=A[j];
 if(sum>maximum)
 maximum=sum;
}
  }
  return maximum;
}

int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum(a,6)<<endl;
return 0;
}

方法二:分治法

设A[i]为数组中的一个任意元素,则对于最大连续子数组和,有:
1、若最大子数组和中包括A[i]这个元素,则从A[i]往左找,找出左边的最大值,再从A[i]往右找,找出右边的最大值,相加即得。
2、若最大子数组和中不包括A[i]这个元素,则最大子数组和是A[0],…A[i-1]和A[i+1]…A[n-1]两个子数组最大和的最大值。
时间复杂度为O(NlgN)。
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum2(int *A,int beg,int end)
{
  if(beg==end)
 return A[beg-1];
  int mid=(beg+end)/2;
  int left=MaxSum2(A,beg,mid);
  int right=MaxSum2(A,mid+1,end);
  int sum=A[mid-1];
  int i=mid-1;
  int j=mid+1;
  int maxsum=A[mid-1];
  while(i>=beg)
  {
    sum+=A[i-1];
    if(sum>maxsum)
maxsum=sum;
i–;
  }
  sum=maxsum;
  while(j<=end)
  {
    sum+=A[j-1];
if(sum>maxsum)
maxsum=sum;
++j;
  }
  int max=left>right?left:right;
  max=max>maxsum?max:maxsum;
  return max;
}


int main()
{
int a[]={1,-2,3,5,-3,2};
cout<<MaxSum2(a,1,6)<<endl;
return 0;
}



方法三:时间复杂度为O(N)。
1.动态规划法
//此处申请了两个数组,需要占用一定的空间。
#include<iostream>
#include<limits.h>
using namespace std;
int max(int x,int y)
{
   return (x>y)?x:y;
}
int MaxSum3(int *A,int n)
{
  int Start[6]={INT_MIN};
  int All[6]={INT_MIN};
  Start[n-1]=A[n-1];
  All[n-1]=A[n-1];
  for(int i=n-2;i>=0;i–)
  {
    Start[i]=max(A[i],A[i]+Start[i+1]);
All[i]=max(Start[i],All[i+1]);
  }
  return All[0];
}
int main()
{
   int a[]={1,-2,3,5,-3,2};
   cout<<MaxSum3(a,6)<<endl;
   return 0;
}

2.改进上面的程序后,只需要O(1)的空间。
#include<iostream>
#include<limits.h>
using namespace std;
int max(int x,int y)
{
   return (x>y)?x:y;
}
int MaxSum3(int *A,int n)
{
  int Start=A[n-1];;
  int All=A[n-1];
  for(int i=n-2;i>=0;i–)
  {
    Start=max(A[i],A[i]+Start);
All=max(Start,All);
  }
  return All;
}
int main()
{
   int a[]={1,-2,3,5,-3,2};
   cout<<MaxSum3(a,6)<<endl;
   return 0;
}

3.另一种写法
#include<iostream>
#include<limits.h>
using namespace std;
int MaxSum4(int *A,int n)
{
  int Start=A[n-1];
  int All=A[n-1];
  for(int i=n-2;i>=0;i–)
  {
    if(Start<0)
Start=0;
Start+=A[i];
if(Start>All)
All=Start;
  }
  return All;
}
int main()
{
   int a[]={1,-2,3,5,-3,2};
   cout<<MaxSum4(a,6)<<endl;
   return 0;
}

扩展问题1:如果考虑到数组首尾相连呢?需要按以下步骤做出改进:
1、先按不相连计算出最大值max
2、从尾往头扫描,找出最大值m1,并记录最大位置i,再从头往尾扫描,找出最大值m2, 并记录最大位置j,若i>j,则比较m1+m2与max,求出最大值,若i<=j,则令m = A[0]+A[1]+A[2]+…A[n-1],求出m和max之间的最大值。
代码为:
#include<iostream>  
using namespace std;  

int max(int a,int b)
{
return a>b?a:b;
}
int min(int a,int b)
{
return a<b?a:b;
}  
/*********************************动态规划************************************** 
假设A[0],A[1],…A(n-1)的最大子段为A[i],…,A[j],则有以下3种情况, 
1)当0=i=j的时候,元素A[0]本身构成和最大的一段 
2)当0=i<j的时候,和最大的一段以A[0]开头 
3)当0<i时候,元素A[0]跟和最大的一段没有关系 
则原始问题A[0],A[1],…A(n-1)的解All[0]=max{A[0],A[0]+Start[1],ALL[1]} 
求得A[0],A[1],…A[n-1](首尾不连接)的情况后再考虑整体思路中的第二种情况 
*********************************************************************************/  
//从尾到首动态规划  
int MaxSum5(int *A,int length){  
    //先求出A[0],A[1],…A[n-1](首尾不连接)的情况下子数组和最大值nAll  
    int nStart=A[length-1];  
    int nAll=A[length-1];  
    for(int i=length-2;i>=0;i–){  
        nStart=max(A[i],A[i]+nStart);  
        nAll=max(nStart,nAll);  
    }  
    //下面处理整体思路的第二种情况,即跨过A[n-1],A[0]  
    //先求A[n-1]结尾的和最大的一段(A[i],…,A[n-1])(0<=i<n)  
    int sum=0;  
    int ltempmax=-10000000;  
    int lpos=length;  
    for(int j=length-1;j>=0;j–){  
        sum+=A[j];  
        if(sum>ltempmax){  
            ltempmax=sum;  
            lpos=j;  
        }  
    }  
  
    //求A[0]开始和最大的一段(A[0],…,A[j])(0<=j<n)  
    sum=0;  
    int rtempmax=-10000000;  
    int rpos=-1;  
    for(int k=0;k<length;k++){  
        sum+=A[k];  
        if(sum>rtempmax){  
            rtempmax=sum;  
            rpos=k;  
        }  
    }  
  
    //如果lpos<=rpos,则循环数组中可能出现的子数组最大值要么是A[0]…A[n-1]子数组和的最大值nAll  
    //要么是整个数组A[0]…A[n-1]的和再减去A[0]…A[n-1]中子数组和为负数的最小值  
    if(lpos<=rpos){  
        //求数组中和为负数且的最小值  
        int minStart=0;  
        int minAll=0;  
        for(int i=0;i<length;i++){  
            minStart=min(0,A[i]+minStart);  
            minAll=min(minStart,minAll);  
        }  
        int tempmax=ltempmax+rtempmax;  
        for(int r=lpos;r<=rpos;r++){  
            tempmax-=A[r];  
        }  
        //比较A[0]…A[n-1]子数组和的最大值nAll跟A[0]…A[n-1]的和再减去A[0]…A[n-1]中子数组和为负数的最小值  
        return max(nAll,tempmax-minAll);  
    }else{  
        //比较A[0]+…+A[j]+A[i]+…+A[n-1]即ltempmax+rtempmax的值跟A[0]…A[n-1]子数组和的最大值nAll  
        return max(nAll,ltempmax+rtempmax);  
    }  
  
}  
int main()
{
   int a[]={1,-2,3,5,-3,2};
   cout<<MaxSum5(a,6)<<endl;
   return 0;
}



扩展问题2:
求数组的子数组之和的最大值并给出子数组的起始终止位置
#include<iostream>
#include<limits.h>
using namespace std;
//动态规划法
int MaxSum5(int *A,int n,int start,int end)
{
  int nStart=A[n-1];
  int nAll=A[n-1];
  start=end=n-1;
  int ostart=start;
  int oend=end;
  for(int i=n-2;i>=0;i–)
  {
    if(A[i]>A[i]+nStart)
{
 start=end=i;
 nStart=A[i];
}
else
{
 start=i;
 nStart=nStart+A[i];
}
if(nStart>nAll){
ostart=start;
oend=end;
nAll=nStart;
}
  }
  start=ostart;
  end=oend;
  cout<<start<<” “<<end<<endl;
  return nAll;
}
int main()
{
   int a[]={1,-2,3,5,-3,2};
   cout<<MaxSum5(a,6,0,5)<<endl;
   return 0;
}

    原文作者:快乐的霖霖
    原文地址: https://blog.csdn.net/chdhust/article/details/8280743
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞