題目:如何求出一個二維數組中的最大子數組之和。
方案一:暴力破解-枚舉法。對於一個二維數組我們列舉出每一個子數組值的大小,然後進行比較,這樣就可以得到最大的和了。其時間復雜度為: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 #include 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:"<> row >> col; cout<<"please input the data of the array:"<> 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 (sindMaxSum) MaxSum = Start+Tail; } cout <<"最大的子矩陣和為:"<< MaxSum<
擴展問題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 #include 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:"<> row >> col >>high; cout<<"please input the data of the array:"<> 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<