數字之魅:子陣列之和的最大值[二維]+[三維]

塵虛緣_KY發表於2016-07-12
題目:如何求出一個二維陣列中的最大子陣列之和。
方案一:暴力破解-列舉法。對於一個二維陣列我們列舉出每一個子陣列值的大小,然後進行比較,這樣就可以得到最大的和了。其時間複雜度為:O(N*N*M*M*Sum的時間複雜度)[N表示行數,M表示列數,Sum是求解子矩陣的和]。由於Sum函式求和也是採用迴圈,足見這個時間複雜度可是相當的大。
方案二:先計算出以左上角的元素(1,1)和當前元素(i,j)為頂點對的子矩陣的部分和,部分和的計算如下圖(一)所示,我們可以看出:以(i_min,j_min),(i_min,j_max),(i_max,j_min),(i_max,j_max)為頂點的矩形區域的元素和為:Sum[i_max][j_max] = Sum[i_max][j_max]-Sum[i_min-1][j_max]-Sum[i_max][j_min-1]+Sum[i_min-1][j_min-1]。也就是在已知部分和的基礎上就可以在0(1)的時間內得到任意矩形的元素之和。
  那麼如何得到部分和呢?
  如圖二所示,在更小的“部分和”基礎上,也能以O(1)得到新的“部分和”。公式為:Sum[i][j]=Sum[i-1][j]+Sum[i][j-1]-Sum[i-1][j-1]+arr[i][j] 這裡的arr[i][j]是矩陣中的arr[i][j]的值。因此在這裡我們可以用O(N*M)來得到部分和。這裡我們主要去掉了計演算法子矩陣和的時間複雜度,所以其時間複雜度為:O(N*N*M*M).相比與方案一有所提高。


            圖一                                                                         圖二

方案三:其實我們仔細的想一想,就可以發現,它的最簡單的原型是求一維陣列中的最大值。那麼基於這個啟發,我們可以把問題從二維轉化為一維以提高演算法效能。假設已經確定了矩陣區域的上下邊界,知道矩陣區域的上下邊界分佈是第a行和第c行,就把每列的第a行和第c行之間的元素看成一個整體。即求陣列:(merge[1],merge[2],merge[3]....).merge[i]=arr[a][i]+arr[b][i]+..+arr[c][i].

這樣我們可以列舉矩形的上下邊界,然後再用一維情況下的方法確定左右邊界,相當於一維陣列中的一個元素(通過子矩陣部分和可以在O(1)時間內計算出整體之和),就可以得到二維問題的解。新方法的時間複雜度為:O(N*N*M)=O(N*N*N)。具體過程如下圖三所示:


圖三

方案一的具體實現程式碼:
int arr[N][M];
int Max(int x,int y)//得到最大值;
{
	return  (x>y)?x:y;
}
int Sum(int imin,int imax,int jmin,int jmax)//得到子矩陣的和,時間複雜度為O(N*N);
{
	int sum=0;
	for(int i=imin;i<=imax;i++)
		for(int j=jmin;j<jmax;j++)
			sum+=arr[i][j];
	return sum;
}
int MaxSum(int *arr,int m,int n) //得到子矩陣的最大值;
{
	int maximum=0;
	for(int i_min=1;i_min<=n;i_min++)
		for(int i_max=i_min;i_max<=n;i_max++)
			for(int j_min=1;j_min<=m;j_max++)
				for(int j_max=j_min;j_max<=m;j_max++)
					maximum=Max(maximum,Sum(i_min,i_max,j_min,j_max));
	return maximum;
}
方案二的具體實現程式碼:
int arr[N][M];
int Sum[N][M];//儲存部分和的陣列;
int Max(int x,int y)//得到最大值;
{
	return  (x>y) ? x:y;
}
void GetSum()//預處理得到部分和;
{
	for(int i=0;i<=n;i++)
		Sum[i][0]=0;//邊界值;
	for(int j=0;j<=n;j++)
		Sum[0][j]=0;//邊界值;真正的部分和資料是從Sum[1][1]開始的;
	for(i=1;i<=n;i++)
		for(j=1;j<=m;j++)
			Sum[i][j]=Sum[i-1][j]+Sum[i][j-1]-Sum[i-1][j-1]+arr[i][j]
}
inline int Sum(int i_min,int i_max,int j_min,int j_max)//呼叫比較頻繁,可設定為行內函數,提高效率。和方案一相比,其時間複雜度為O(1).
{
	return Sum[i_max][j_max]-Sum[i_min-1][j_max]-Sum[i_max][j_min-1]+Sum[i_min-1][j_min-1];
}
int MaxSum(int *arr,int m,int n)
{
	int maximum=0;
	for(int i_min=1;i_min<=n;i_min++)
		for(int i_max=i_min;i_max<=n;i_max++)
			for(int j_min=1;j_min<=m;j_max++)
				for(int j_max=j_min;j_max<=m;j_max++)
					maximum=Max(maximum,Sum(i_min,i_max,j_min,j_max));
	return maximum;
}
方案三的具體實現程式碼:
#include <iostream>  
#include <algorithm>  
using namespace std;  
#define LEN 888 
int arr[LEN][LEN];  
long long Sum[LEN][LEN];  


inline long long MatrixSum(int s, int t, int i, int j)  
{  
	return Sum[i][j]-Sum[i][t-1]-Sum[s-1][j]+Sum[s-1][t-1];  
}  


int main()  
{  
	int row, col, i, j;  
	cout<<"please input the row and col of the array:"<<endl;
	cin >> row >> col;  
	cout<<"please input the data of the array:"<<endl;
	for (i=1; i<=row; i++)  //輸入資料;
		for (j=1; j<=col; j++)  
			cin >> arr[i][j];  


	for (i=0; i<=row; i++)  //設定邊界線;
		Sum[i][0] = 0;  
	for (j=0; j<=col; j++)  
		Sum[0][j] = 0;  


	for (i=1; i<=row; i++)  // 預處理計算矩陣的部分和 
		for (j=1; j<=col; j++)  
			Sum[i][j] = arr[i][j]+Sum[i-1][j]+Sum[i][j-1]-Sum[i-1][j-1];  


	int a, c;  
	long long MaxSum = arr[1][1]; //初始值的設定; 
	for (a=1; a<=row; a++)  
		for (c=a; c<=col; c++)  // 將子矩陣上下邊界設為第a行和第c行,求取取最大值  
		{  
			long long Tail = MatrixSum(a, 1, c, 1);  //合併列;
			for (j=2; j<=col; j++)  //按一維陣列向後列舉;
			{  
				Tail = max( MatrixSum(a, j, c, j), MatrixSum(a, j, c, j)+Tail);   
				MaxSum = max(Tail, MaxSum);  
			}  
		}  


		cout <<"最大的子矩陣和為:"<< MaxSum<<endl;  
		system("pause");
		return 0;
}  
執行結果如下:


擴充套件問題1:如果二維陣列也是首尾相連,像一條首尾相連的帶子,演算法會如何改變?
其實現程式碼如下:
#include <iostream>  
#include <algorithm>  
using namespace std;  
#define LEN 1003  
int arr[LEN][LEN];  
long long Sum[LEN][LEN];  


inline long long MatrixSum(int s, int t, int i, int j)  
{  
	return Sum[i][j]-Sum[i][t-1]-Sum[s-1][j]+Sum[s-1][t-1];  
}  


int main()  
{  
	int  row, col, i, j;  
	cout<<"please input the row and col of the array:"<<endl;
	cin >> row >> col;  
	cout<<"please input the data of the array:"<<endl; 
	for (i=1; i<=row; i++)  
		for (j=1; j<=col; j++)  
			cin >> arr[i][j];  
	for (i=0; i<=row; i++)  
		Sum[i][0] = 0;  
	for (j=0; j<=col; j++)  
		Sum[0][j] = 0;  
	// 計算矩陣的部分和  
	for (i=1; i<=row; i++)  //計算部分和;
		for (j=1; j<=col; j++)  
			Sum[i][j] = arr[i][j]+Sum[i-1][j]+Sum[i][j-1]-Sum[i-1][j-1];  
	int a, c;  
	long long MaxSum = arr[1][1];  	// 上下邊界不會跨過第n行和第1行  
	for (a=1; a<=row; a++)  
		for (c=a; c<=row; c++)  
		{     
			// 將子矩陣上下邊界設為第a行和第c行  
			// 左右邊界不會跨過第m列和第1列  
			long long Tail = MatrixSum(a, 1, c, 1);  
			for (j=2; j<=col; j++)  
			{  
				Tail = max(MatrixSum(a, j, c, j),   
					MatrixSum(a, j, c, j)+Tail);   
				MaxSum = max(Tail, MaxSum);  
			}  
			long long Sum = MatrixSum(a, 1, c, 1);  // 左右邊界會跨過第n列和第1列 
			long long Start = Sum;  
			int sind = 1;  
			for (i=2; i<=col; i++)  
			{  
				Sum += MatrixSum(a, i, c, i);  
				if (Sum > Start) {Start = Sum; sind = i;}  
			}  
			Tail = MatrixSum(a, col, c, col);  
			int tind = col;  
			for (j=col-1; j>=1; j--)  
			{  
				Sum += MatrixSum(a, j, c, j);  
				if (Sum > Tail) {Tail = Sum; tind = j;}  
			}  
			if (sind<tind && Start+Tail>MaxSum)  
				MaxSum = Start+Tail;  
		}  
		cout <<"最大的子矩陣和為:"<< MaxSum<<endl;  
		system("pause");
		return 0;
}  
擴充套件問題2:求3維矩陣,也就是長方體中子長方體的最大值?
長方體我們需要求子長方體的最大值,那麼可以想象一下!其實思路和二維是一樣的,主要是採取降維的思路,這樣可以把複雜的問題簡單化。主要關鍵還是在求部分和,其求解公式如下:
   PS[i][j][k] = A[i][j][k]+PS[i-1][j][k]+PS[i][j-1][k]+PS[i][j][k-1]-PS[i-1][j-1][k]-PS[i-1][j][k-1]-PS[i][j-1][k-1]+PS[i-1][j-1][k-1];  [i,j,k形象點兒可分別代表其長寬高]
  其時間複雜度為:O(N*N*M*M*Q)=O(N^5)。可以得出:一維是O(N),二維是O(N*N),三維是O(N^5).
具體實現程式碼如下:
#include <iostream>  
#include <algorithm>  
using namespace std;  

#define LEN 500  
int arr[LEN][LEN][LEN];  
int Sum[LEN][LEN][LEN];  

inline int MaxCubeSum(int a, int b, int c, int d, int i, int j)  
{  
	return Sum[b][d][j]-Sum[a-1][d][j]-Sum[b][c-1][j]-Sum[b][d][i-1]+  
		Sum[a-1][c-1][j]+Sum[a-1][d][i-1]+Sum[b][c-1][i-1]-Sum[a-1][c-1][i-1];  
}  
int main()  
{  
	int row, col, high, i, j, k;  
	cout<<"please input the row, col and high of the array:"<<endl;
	cin >> row >> col >>high;  
	cout<<"please input the data of the array:"<<endl; 
	for (i=1; i<=row; i++)  
		for (j=1; j<=col; j++)  
			for (k=1; k<=high; k++)  
				cin >> arr[i][j][k];  
	for (i=0; i<=row; i++)  
		for (j=0; j<=col; j++)  
			Sum[i][j][0] = 0;  
	for (i=0; i<=row; i++)  
		for (k=0; k<=high; k++)  
			Sum[i][0][k] = 0;  
	for (j=0; j<=col ; j++)  
		for (k=0; k<=high; k++)  
			Sum[0][j][k] = 0;  
	// 計算長方體的部分和  
	for (i=1; i<=row; i++)  
		for (j=1; j<=col; j++)  
			for (k=1; k<=high; k++)  
				Sum[i][j][k] = arr[i][j][k]+Sum[i-1][j][k]+Sum[i][j-1][k]+Sum[i][j][k-1]-Sum[i-1][j-1][k]-Sum[i-1][j][k-1]-Sum[i][j-1][k-1]+Sum[i-1][j-1][k-1];  
	int a, b, c, d;  
	int MaxSum = arr[1][1][1];  
	// 限制第一維的取值範圍  
	for (a=1; a<=row; a++)  
		for (b=a; b<=row; b++)  
			// 限制第二維的取值範圍  
			for (c=1; c<=col; c++)  
				for (d=c; d<=col; d++)  
				{  
					// 只剩下最後一維沒有確定,利用一維部分和的方法  
					int Tail = MaxCubeSum(a,b,c,d,1,1);  
					for (j=2; j<=k; j++)  
					{  
						int cur = MaxCubeSum(a,b,c,d,j,j);  
						Tail = max(Tail+cur, cur);  
						MaxSum = max(Tail, MaxSum);  
					}  
				}  
				cout <<"最大的子矩陣和為:"<< MaxSum<<endl;  
				system("pause");
				return 0;
}  


相關文章