1. 程式人生 > >求陣列的連續子陣列之和的最大值(一維二維)

求陣列的連續子陣列之和的最大值(一維二維)

求陣列的連續子陣列之和的最大值
輸入一個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]
編碼開始
 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種可能:
原始陣列最大和就是array[0-N/2-1]陣列的最大和
原始陣列最大和就是array[N/2-N-1]陣列的最大和
最大和的子陣列是包括array[N/2-1]array[N/2]的一段子陣列。
前兩種情況可以用遞迴解決,第三種情況從Mid向兩邊遍歷一次O(n)就可以了。
遞迴要遞迴到只有一個元素的時候直接返回即可。時間複雜度很明顯是
O(nlgn)
編碼實現:
  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; //最大值初始化為極小值
  	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]編碼開始
 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]的最大值即可,很多值就是一個過渡,一個變數足以解決問題。
程式碼修改
 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那就沒必要要他了,直接捨棄。
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;
}
測試程式碼
#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[N]={-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],是嗎?這個時候我們還要判斷ij的大小。如果i<=j那我們要求的就是 Sum=array[0]+....+array[N-1],否則Sum=array[0]+...array[j]+array[i]+....+array[N-1]。這個程式碼應該很好寫,因為有了剛才的鋪墊。
這個時候我再問你,如果我要返回下標,你要怎麼做?無非是傳入兩個座標值,每次求最大和的時候記錄下來即可。沒問題的。程式碼就不寫了。
OK,一維陣列咱算是說完了,那咱來看看二維的怎麼搞?
二維陣列連續的二維子陣列的和怎麼求,肯定是一個矩形,我們要遍歷嗎???
遍歷的話估計複雜度扛不住吧。。如何遍歷也是一個問題。
這個時候我們可以把每一行看成是一個元素,這樣就變成了一個縱向的一維陣列了。

這樣對一維陣列的遍歷是和剛才一樣的。而對於每一行我們遍歷也是和一維是一樣的。編碼試一試
 //求二維陣列的連續子陣列之和的最大值
  int MaxSum(int (*array)[N])
  {
  	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]代表了下標(00)(0j)(i0)(ij)包圍的區間的和。

而此時下標(iminjmin)(iminjmax)(imaxjmin)(imaxjmax)包圍的區間和就等於
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]我們還是要另外處理,這就比較麻煩了。我們還是用預處理的方法來編碼吧。。
  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]
編碼開始:
//求二維陣列的連續子陣列之和的最大值
int MaxSum(int (*array)[N])
{
	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來做,這個也可以嗎?可以的。我們把每一列看成一個元素。這樣對於遍歷的行區間,我們就可以當成一維來做。

對於iminimax之間的每一列,就相當於一維的一個元素。
假設這個一維陣列是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,編碼開始
//求二維陣列的連續子陣列之和的最大值
int MaxSum(int (*array)[N])
{
	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)吧。
這個時候我要問你,如果也把二維的陣列首尾相連,你要怎麼求最大值。方法和一維的類似,略有不同吧。
如果對於三維陣列求一個長方體的最大和,你怎麼求?
 如果上下也相連怎麼辦???
四維呢??
這次你可要好好思考了。先說到這吧。
轉載請註明出處http://blog.csdn.net/sustliangbo/article/details/9411335