【神一样的作业】二维数组连续的二维子数组的和(元素可以连续)
结对编程训练
——小组成员:刘铸辉 何晓楠
之前做二维数组连续的二维子数组的和的时候有问题,当时不是很懂,这次跟新问题一起来跟大家讨论下。
扩展问题一:
二维数组连续的二维子数组的和(矩形)
首先子数组是个矩形,肯定要先遍历,而且复杂度以很高,如何遍历也是一个问题。
方法1 遍历
这个时候我们可以把每一行看成是一个元素,这样就变成了一个纵向的一维数组了。这样对一维数组的遍历是和以前做的是一样的。而对于每一行我们遍历也是和一维是一样的。编码如下:
1 //求二维数组的连续子数组之和的最大值 2 int MaxSum(int (*array)[N]) 3 { 4 int i,j; 5 int MaxSum=-INFINITY;//初始化 6 int imin,imax,jmin,jmax; 7 for(imin=1;imin<=N;imin++) 8 { 9 for(imax=imin;imax<=N;imax++)//当成是遍历纵向的一维 10 { 11 for(jmin=1;jmin<=M;jmin++) 12 { 13 for(jmax=jmin;jmax<=M;jmax++)//当成是遍历横向的一维 14 MaxSum=MaxNum(MaxSum,PartSum(imin,jmin,imax,jmax)); 15 } 16 } 17 } 18 return MaxSum; 19 }
方法2 先求部分和
求二维的时候就变成了四个坐标了,不能遍历求和了。可以先求部分和,把他当作已知的,然后定义一个部分和数组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]。
1 int MaxSum(int (*array)[N]) 2 { 3 int PartSum[N+1][M+1]; 4 int i,j; 5 for(i=0;i<=N;i++) 6 PartSum[i][0]=0; 7 for(j=0;j<=M;j++) 8 PartSum[0][j]=0; 9 for(i=1;i<=N;i++) 10 for(j=1;j<=M;j++) 11 PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1]; 12 int MaxSum=-INFINITY;//初始化 13 int imin,imax,jmin,jmax; 14 for(imin=1;imin<=N;imin++) 15 for(imax=imin;imax<=N;imax++) 16 for(jmin=1;jmin<=M;jmin++) 17 for(jmax=jmin;jmax<=M;jmax++) 18 MaxSum=MaxNum(MaxSum,PartSum[imax][jmax]-PartSum[imin-1][jmax]-PartSum[imax][jmin-1]+PartSum[imin-1][jmin-1]); 19 20 return MaxSum; 21 }
方法3动态规划
把每一列看成一个元素。这样对于遍历的行区间,我们就可以当成一维来做。对于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是一个函数)
1 //求二维数组的连续子数组之和的最大值 2 int MaxSum(int (*array)[N]) 3 { 4 int PartSum[N+1][M+1]; 5 int i,j; 6 for(i=0;i<=N;i++) 7 PartSum[i][0]=0; 8 for(j=0;j<=M;j++) 9 PartSum[0][j]=0; 10 for(i=1;i<=N;i++) 11 for(j=1;j<=M;j++) 12 PartSum[i][j]=PartSum[i-1][j]+PartSum[i][j-1]-PartSum[i-1][j-1]+array[i-1][j-1]; 13 int MaxSum=-INFINITY; 14 int Start,All; 15 int imin,imax; 16 for(imin=1;imin<=N;imin++) 17 { 18 for(imax=imin;imax<=N;imax++) 19 { 20 Start=BC(PartSum,imin,imax,M); 21 All=BC(PartSum,imin,imax,M); 22 for(j=M-1;j>=1;j--) 23 { 24 if(Start>0) 25 Start+=BC(PartSum,imin,imax,j); 26 else 27 Start=BC(PartSum,imin,imax,j); 28 if(Start>All) 29 All=Start; 30 } 31 if(All>MaxSum) 32 MaxSum=All; 33 } 34 } 35 return MaxSum; 36 } 37 38 int BC(int (*PartSum)[N+1],int imin,int imax,int j) //imin--imax第j列的和 39 { 40 int value; 41 value=PartSum[imax][j]-PartSum[imin-1][j]-PartSum[imax][j-1]+PartSum[imin-1][j-1]; 42 return value; 43 }
这样就算是把二维数组连续的二维子数组的和问题解决了,但是这只是求矩形子数组。
扩展问题二:
二维数组连续的二维子数组的和(左右相连,上下也相连)
分析思路:这个最大子矩阵和就是把平面上下相连和左右相连,无论先连哪一边都一样。先随便找一条横边和一条竖边即可,然后原问题等价于把4个这样的平面拼在一起,然后找不超过一个平面大小的最大子矩阵。先转换为1维的问题,枚举上下边界,用一维的方法用单调队列求解。编码如下:
1 int main() 2 { 3 int m,n; 4 scanf("%d%d",&m,&n); 5 int temp[101][101]; 6 int result[10000]; 7 int s[202][202]; 8 int i,j,p,q; 9 10 memset(temp,0,sizeof(temp)); 11 memset(s,0,sizeof(s)); 12 memset(result,0,sizeof(result)); 13 //输入二维数组 14 for( i=1; i<=m; i++) 15 for( j=1; j<=n; j++) 16 { 17 scanf("%d",&s[i][j]); 18 s[i][n+j] = s[i][j]; 19 s[m+i][j] = s[i][j]; 20 s[m+i][n+j] = s[i][j]; 21 } 22 int r =0; 23 for( i=0; i<m;i++) 24 for( j=0; j<n;j++) 25 { 26 for( p=1; p<=m; p++) 27 { 28 for(q=1; q<=n; q++) 29 temp[p][q] = s[p+i][q+j]; 30 } 31 result[r++] = MaxSum2(m,n,temp); //记录各种情况下的最大子矩阵和 32 } 33 34 int maxresult = result[0]; //从各种情况下的最大子矩阵和中找出最大的值 35 for (i=1; i<m;i++) 36 { 37 if(maxresult < result[i]) 38 maxresult = result[i]; 39 } 40 printf("%d\n",maxresult); 41 return 0; 42 } 43 44 //用动态规划算法计算“最大子段和问题”,对应于一维数组 45 int MaxSum(int n,int* a) 46 { 47 int sum=0,b=0; 48 for (int i=1;i<=n;i++) 49 { 50 if (b>0) b+=a[i]; 51 else b=a[i]; 52 if (b>sum) sum=b; 53 } 54 return sum; 55 }
1 int MaxSum2(int m, int n, int a[][101]) 2 { 3 int sum=0; 4 int b[101]; 5 int i,j,k,p,t1=0,t2=0; 6 memset(b,0,sizeof(b)); //内存空间初始化 7 for ( i=1; i<=m; i++) 8 { 9 int flag_m = m , flag_n = n; 10 for( k=1; k<=2*n; k++) 11 b[k]=0; 12 for ( j=i; j < flag_m + i; j++) 13 { 14 t1 = j>m? j-m : j; 15 for( k=1; k <=n ; k++) 16 { 17 t2 = k>n? k-n : k; 18 b[k] += a[t1][t2]; 19 } 20 int max = MaxSum(2*n,b); 21 if(max>sum) sum = max; 22 23 } 24 } 25 return sum; 26 }
有些思路也是从网上借鉴的,看了很多种解法,然后结合自己的解法跟大家分享下。