题目:如何求出一个二维数组中的最大子数组之和。
方案一:暴力破解-枚举法。对于一个二维数组我们列举出每一个子数组值的大小,然后进行比较,这样就可以得到最大的和了。其时间复杂度为: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; }