您的位置:首页 > 其它

一维数组和二维数组的最大连续子数组问题

2018-04-10 21:10 465 查看
http://blog.csdn.net/liangbopirates/article/details/9411335
求数组的连续子数组之和的最大值
输入一个N个元素的整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。
例如输入的数组为-9  -3  -2  2  -1  2  5  -7  1  5,和最大的子数组为2  -1  2  5。因此输出为该子数组的和8。
可是如果都是负数的话,要返回0?还是返回最小的负数?,这个数时候你要问问面试官(交流很重要)。
OK,想一想该如何做?遍历?遍历是最伟大的方法,几乎多有的题目都可解答,堪称万能解题法。
好吧,咱就先用遍历来解决一下。Sum[i-j]代表i-j元素的和,需要N次遍历,每一次遍历都要求Sum[i-j]。
编码开始
[cpp] view plain copy int MaxSum(int array[],int start,int end)   {        int MaxSum=-INFINITY;//最大值初始化为极小值      int sum=0;      for(int i=start;i<=end;i++)      {          sum=0; //记得每次初始化,我们求的是子数组的和          for(int j=i;j<=end;j++)          {              sum+=array[j];              if(sum>MaxSum)                  MaxSum=sum;          }      }      return MaxSum;   }  时间复杂度是O(n^2)。还凑合吧,还能继续优化吗?想一想,之前学过的方法。分治法,分而治之,可以吗?YES如果我们把数组array[1-N]分成两部分array[0-N/2-1]和array[N/2-N-1],那么我们要求的连续子数组最大和就有了3种可能:1 原始数组最大和就是array[0-N/2-1]数组的最大和2 原始数组最大和就是array[N/2-N-1]数组的最大和3 最大和的子数组是包括array[N/2-1]和array[N/2]的一段子数组。前两种情况可以用递归解决,第三种情况从Mid向两边遍历一次O(n)就可以了。
递归要递归到只有一个元素的时候直接返回即可。时间复杂度很明显是O(nlgn)。
编码实现:
[cpp] view plain copy int MaxSum(int array[],int start,int end)  {      int LeftSum,RightSum,MidLeftSum,MidRightSum;      int max,sum;      int i;      if(end==start)          return array[start];      int pivot=start+(end-start)/2;       LeftSum=MaxSum(array,start,pivot);      RightSum=MaxSum(array,pivot+1,end);      max=-INFINITY; <span style="font-family:Arial,Helvetica,sans-serif">//最大值初始化为极小值</span>      sum=0;      for(i=(end-start+1)/2-1;i>=start;i--)  //求以array[N/2-1]结尾的一段最大和      {          sum+=array[i];          if(sum>max)              max=sum;      }      MidLeftSum=max;      max=-INFINITY;      sum=0;      for(i=(end-start+1)/2;i<=end;i++)  //求以array[N/2]开始的一段最大和      {          sum+=array[i];          if(sum>max)              max=sum;      }      MidRightSum=max;      return Max(LeftSum,MidLeftSum+MidRightSum,RightSum);  }  时间复杂度降到了O(nlgn),还不错。可是我们不能仅仅满足于这样的复杂度,O(n)才是我们追求的目标。可是这个题目可以达到吗?想一想,这个题目有什么性质?最优子结构?YES。原数组的最优解包含子数组的最优解。数组array[0-N-1]的最优解肯定包含子数组array[1-N-1]的最优解。数组array[0-N-1]的最优解与谁有关系?要么是array[0],要么是子数组array[1-N-1]的最优解,要么是两者之和。而且在求最大和的时候会出现很多的重叠子问题。比如求array[0-N-1]和array[1-N-1]的最优解肯定都要求array[2-N-1]的最优解。而且满足无后效性,对于当前求得array[0-N-1]的最优解只和array[1-N-1]的最优解有关系,和array[1-N-1]的值没有关系,唯一影响的就是array[0]。这样我们就知道可以用DP来做。
假设用数组All[i]表示array[i-N-1]的最大和,用数组Start[i]表示包含arry[i]的array[i-N-1]的最大和。那我们要求array[i-N-1]的最大值就是All[i],Start[i],array[i]三者中的最大值。而Start[i]如何求呢?要么等于array[i],要么等于array[i]+Start[i+1]。而All[i]要么和array[i]有关系要么没关系,所以要么等于Start[i],,要么等于All[i+1]。
这样状态转移方程就来了:
Start[i]=MaxNum(array[i],array[i]+Start[i+1]);
All[i]=MaxNum(Start[i],All[i+1]);
我们要求的最大值就是All[0]。
编码开始
[cpp] view plain copy int MaxSum(int array[],int start,int end)   {      int arraylength=end-start+1;      int *Start=new int[arraylength];      int *All=new int[arraylength];      Start[arraylength-1]=array[end]; //注意arraylength-1=end不是什么时候都成立的//      All[arraylength-1]=array[end];      for(int i=end-1;i>=start;i--)      {          Start[i]=MaxNum(array[i],array[i]+Start[i+1]);          All[i]=MaxNum(Start[i],All[i+1]);         }      return All[0];   }  时间复杂度当然是O(n)。因为我们有O(n)个子问题(array[0-i]),对于每一子问题我们有3种选择。这样就算法就比较nice了。可是空间复杂度很大啊,辅助空间就要O(n)。可以降低吗?仔细分析下刚才所写的代码,你能发现什么问题吗?没必要定义辅助数组?YES。因为我们最后要求的只是数组中的一个值。我们只要求这三个array[i],rray[i]+Start[i+1],All[i+1]的最大值即可,很多值就是一个过渡,一个变量足以解决问题。
代码修改
[cpp] view plain copy int MaxSum(int array[],int start,int end)   {      int Start,All;      Start=array[end];       All=array[end];      for(int i=end-1;i>=start;i--)      {          Start=MaxNum(array[i],array[i]+Start);          All=MaxNum(Start,All);      }      return All;   }  
辅助空间是O(1)。很不错
可是我们可以换个写法,如果Start[i]<0那就没必要要他了,直接舍弃。
[cpp] view plain copy int MaxSum(int array[],int start,int end)  {      int Start,All;      Start=array[end];       All=array[end];      for(int i=end-1;i>=start;i--)      {          if(Start>=0)              Start+=array[i];          else              Start=array[i];          if(Start>All)              All=Start;      }      return All;  }  测试代码[cpp] view plain copy #include <iostream>     using namespace std;      const int N=10;  const int INFINITY=1000;//定义一个最大值    //求数组的连续子数组之和的最大值  int MaxSum(int array[],int start,int end);  int MaxNum(int x,int y);  int Max(int x,int y,int z);  int main()  {         int i;      int array={-9,-3,-2,-2,-1,-2,-5,-7,-1,-5};      for(i=0;i<N;i++)          cout<<array[i]<<" ";      cout<<endl;      cout<<"连续子数组之和的最大值MaxSum="<<MaxSum(array,0,N-1)<<endl;      return 0;  }  
OK,这样才算是完美解决。可是,这个时候我要问你,如果一维数组首尾相连怎么办?
	这个时候要分情况了,如果最优解没有跨过array[N-1]到array[0],那就是原问题的解。如果跨过,那么我们要遍历一次就可以了。Sum=array[i]+....+array[N-1]+array[0]+...array[j],是吗?这个时候我们还要判断i和j的大小。如果i<=j那我们要求的就是 Sum=array[0]+....+array[N-1],否则Sum=array[0]+...array[j]+array[i]+....+array[N-1]。这个代码应该很好写,因为有了刚才的铺垫。这个时候我再问你,如果我要返回下标,你要怎么做?无非是传入两个坐标值,每次求最大和的时候记录下来即可。没问题的。代码就不写了。
OK,一维数组咱算是说完了,那咱来看看二维的怎么搞?二维数组连续的二维子数组的和怎么求,肯定是一个矩形,我们要遍历吗???遍历的话估计复杂度扛不住吧。。如何遍历也是一个问题。
这个时候我们可以把每一行看成是一个元素,这样就变成了一个纵向的一维数组了。这样对一维数组的遍历是和刚才一样的。而对于每一行我们遍历也是和一维是一样的。编码试一试[cpp] view plain copy //求二维数组的连续子数组之和的最大值   int MaxSum(int (*array))   {      int i,j;      int MaxSum=-INFINITY;//初始化      int imin,imax,jmin,jmax;      for(imin=1;imin<=N;imin++)   {          for(imax=imin;imax<=N;imax++)//当成是遍历纵向的一维   {              for(jmin=1;jmin<=M;jmin++)   {                  for(jmax=jmin;jmax<=M;jmax++)//当成是遍历横向的一维                          MaxSum=MaxNum(MaxSum,PartSum(imin,jmin,imax,jmax));              }   }   }                return MaxSum;   }  
	时间复杂度(N^2*M^2*O(PartSum)),如何求部分和PartSum呢?如果这个还是要遍历求的话,复杂度真是不敢看了。。求一维的时候我们求Sum[i-j]很好求,可是求二维的时候就变成了四个坐标了,不敢遍历求和了。我们可以先求部分和,把他当作已知的,这个时候遍历求的时候复杂度就是O(1)。如何求?我们定义一个部分和数组PartSum,其中PartSum[i][[j]代表了下标(0,0),(0,j),(i,0),(i,j)包围的区间的和。
而此时下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和就等于
PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。
这就是我们要求的PartSum(imin,jmin,imax,jmax),接下来就是求PartSum数组了。如何求呢?
对于每一个PartSum[i][[j]都不是孤立的,都是和其他的有关系的。我们要找出这个关系式
PartSum[i][[j]=PartSum[i-1][[j]+PartSum[i][[j-1]-PartSum[i-1][[j-1]+array[i][j]。这样求可以求出全部的PartSum[i][[j],可是我们不要忽略了一点,PartSum[0][[0]=?对于边界值我们要处理好,而且下标要从1开始。对于PartSum[i][[0]和PartSum[0][[j]都要初始化0,而且array[i][j]的下标也是要-1,因为数组的下标是从0开始的。这是一个办法,不过我们也可以单独求PartSum[i][[0]和PartSum[0][[j]的值,连续相加即可,然后再求其他的也是可以的,空间复杂度也是一样。可是在4重遍历的时候对于PartSum[i][[0]和PartSum[0][[j]我们还是要另外处理,这就比较麻烦了。我们还是用预处理的方法来编码吧。。
[cpp] view plain copy int PartSum[N+1][M+1];      int i,j;      for(i=0;i<=N;i++)          PartSum[i][0]=0;      for(j=0;j<=M;j++)          PartSum[0][j]=0;      for(i=1;i<=N;i++)          for(j=1;j<=M;j++)          PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];  OK,求得部分和之后我们就开始完善我们的编码了。记住一点,下标(imin,jmin),(imin,jmax),(imax,jmin),(imax,jmax)包围的区间和等于
	PartSum[imax][[jmax]-PartSum[imin-1][[jmax]-PartSum[imax][[jmin-1]+PartSum[imin-1][[jmin-1]。
编码开始:[cpp] view plain copy //求二维数组的连续子数组之和的最大值  int MaxSum(int (*array))  {      int PartSum[N+1][M+1];      int i,j;      for(i=0;i<=N;i++)          PartSum[i][0]=0;      for(j=0;j<=M;j++)          PartSum[0][j]=0;      for(i=1;i<=N;i++)          for(j=1;j<=M;j++)              PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];      int MaxSum=-INFINITY;//初始化      int imin,imax,jmin,jmax;      for(imin=1;imin<=N;imin++)          for(imax=imin;imax<=N;imax++)              for(jmin=1;jmin<=M;jmin++)                  for(jmax=jmin;jmax<=M;jmax++)                          MaxSum=MaxNum(MaxSum,PartSum[imax][jmax]-PartSum[imin-1][jmax]-PartSum[imax][jmin-1]+PartSum[imin-1][jmin-1]);                                return MaxSum;  }  
时间复杂度是O(N^2*M^2),有点坑啊。想一想一维的时候我们用DP来做,这个也可以吗?可以的。我们把每一列看成一个元素。这样对于遍历的行区间,我们就可以当成一维来做。
对于imin和imax之间的每一列,就相当于一维的一个元素。假设这个一维数组是BC,则BC[j]=array[imin][j]+....+array[imax][j],问题就变成了求BC数组的连续子数组之和的最大值了。而根据刚才求的部分和,我们可以知道对于imin行和imax行之间的区间第j列的值是BC(PartSum,imin,imax,j)=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1].(此时BC是一个函数)OK,编码开始[cpp] view plain copy //求二维数组的连续子数组之和的最大值  int MaxSum(int (*array))  {      int PartSum[N+1][M+1];      int i,j;      for(i=0;i<=N;i++)          PartSum[i][0]=0;      for(j=0;j<=M;j++)          PartSum[0][j]=0;      for(i=1;i<=N;i++)          for(j=1;j<=M;j++)              PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1];      int MaxSum=-INFINITY;      int Start,All;      int imin,imax;      for(imin=1;imin<=N;imin++)      {          for(imax=imin;imax<=N;imax++)          {              Start=BC(PartSum,imin,imax,M);              All=BC(PartSum,imin,imax,M);              for(j=M-1;j>=1;j--)              {                  if(Start>0)                      Start+=BC(PartSum,imin,imax,j);                  else                      Start=BC(PartSum,imin,imax,j);                  if(Start>All)                      All=Start;              }              if(All>MaxSum)                  MaxSum=All;          }      }      return MaxSum;  }    int BC(int (*PartSum)[N+1],int imin,int imax,int j) //imin--imax第j列的和  {      int value;      value=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1];      return value;  }  
	OK,very nice.时间辅助度降到O(N*M*min(M,N)),差不多O(N^3)吧。
	这个时候我要问你,如果也把二维的数组首尾相连,你要怎么求最大值。方法和一维的类似,略有不同吧。如果对于三维数组求一个长方体的最大和,你怎么求?
	 如果上下也相连怎么办???
四维呢??
这次你可要好好思考了。先说到这吧。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: