编程之美--2.15子数组之和的最大值(二维)
与2.14类似,2.14分析了一维数组的子数组之和的最大值的问题,那么如果是二维数组怎么分析?
解法1 最原始的方法,四重循环计算,时间复杂度O(N^2*M^2*sum的时间复杂度)
解法2 时间复杂度为O(N *M * min(N,M))
方法:枚举矩形上下边界,然后利用一维情况下的方法确定左右边界,就可以得到二维问题的解。(也可枚举左右边界,利用以为情况下的方法确定上下边界)

如图所示,把每一列中第a行和第c行之间的元素看成一个整体。即为求数组BC[0],BC[1]...BC[M-1]中最大的一段。其中BC[i]=arr[a][i]+...+arr[c][i]
main()
public static void main(String args[]){ int[][] arr = { {1,-2,3}, {4,5,6}, {7,8,-9}, {10,11,-12}}; System.out.println("行数:"+arr.length); System.out.println("列数:"+arr[0].length); System.out.println("获得 部分和:"); int[][] psum = GetPartSum(arr); for(int i = 0 ; i <arr.length ; i ++){ for(int j = 0 ; j <arr[0].length ; j++){ System.out.print(psum[i][j]+"\t"); } System.out.println(); } int maxsum = MaxSum(arr,arr.length,arr[0].length); System.out.println("最大值为:"+maxsum); }
获取二维数组最大部分和
方法:枚举矩形上下边界,然后利用一维情况下的方法确定左右边界,就可以得到二维问题的解。(也可枚举左右边界,利用以为情况下的方法确定上下边界)
//n代表行数目,m代表列数目 public static int MaxSum(int[][] arr,int n , int m ){ int mmax = Integer.MIN_VALUE; int start ; int all ; int[][] colsum = GetColSum(arr); for(int a = 0 ; a < n ; a++){ for(int c = a ; c <n ; c++){ start = BC(colsum,a,c,m-1); all = BC(colsum,a,c,m-1); //System.out.println("当前start:"+start+" 当前all:"+all); for(int i = m-2 ; i >=0 ; i--){ if(start < 0)//等价于start-BC(colsum,a,c,i)<BC(colsum,a,c,i),这里和一维情况是类似的 start = 0; start += BC(colsum,a,c,i); if(start>all) all=start; //System.out.println("当前start:"+start+" 当前all:"+all); } if(all>mmax){ mmax = all; //System.out.println("当前最大值:"+mmax); } //System.out.println("==="); } } return mmax; }
获取第a行和第c行之间的第i列的所有元素的和,a、c、i都从0开始
在这里,我们把第a行和第c行之间的每一列看成一个整体,相当于一维数组中的一个元素(通过子矩阵部分和可以在O(1)时间内计算出整体之和)。
//求在第a行和第c行之间的第i列的所有元素的和,a、c、i都从0开始 public static int BC(int[][] psum , int a , int c , int i){ int ssum = 0; if(i==0){ if(a!=0) ssum = psum[c][i]-psum[a-1][i]; else ssum = psum[c][i]; } else{ if(a!=0) ssum = psum[c][i]-psum[c][i-1]-psum[a-1][i]+psum[a-1][i-1]; else ssum = psum[c][i]-psum[c][i-1]; } return ssum; }
获取部分和:GetPartSum
这里的部分和指的是以左上角的元素(0,0)和当前元素(i,j)为顶点对的子矩阵的部分和,部分和的计算如下
PS[i][j] = PS[i-1][j]+PS[i][j-1]-PS[i-1][j-1]+A[i][j]
//获取部分和的方法 public static int[][] GetPartSum(int[][] arr){ int n = arr.length; //表示有n行 int m = arr[0].length; //表示有m列 int psum[][] = new int[n][m]; //创建一个与arr大小完全相同的数组存放部分和 psum[0][0] = arr[0][0]; for(int i = 1 ; i <n ; i++){ psum[i][0] = psum[i-1][0]+arr[i][0]; } for(int i = 1 ; i < m ; i++){ psum[0][i] = psum[0][i-1]+arr[0][i]; } for(int i = 1 ; i <n ; i++){ for(int j = 1 ; j < m ; j++){ psum[i][j] = psum[i-1][j]+psum[i][j-1]-psum[i-1][j-1]+arr[i][j]; } } return psum; }
例子:
int[][] arr = { {1,-2,3},{4,5,6},{7,8,-9},{10,11,-12}};
运行结果:
行数:4
列数:3
获得 部分和:
1 -1 2
5 8 17
12 23 23
22 44 32
最大值为:45
注意:这里其实不使用部分和也行,可以将部分和矩阵psum[i][j]表示第j列的第0行的元素加到第i行元素的和。方法见下面
---------------------------------------
解法2改
将解法2中的BC(int[][] colsum , int a , int c , int i)和GetColSum(int[][] arr)修改如下:
//求在第a行和第c行之间的第i列的所有元素的和,a、c、i都从0开始 public static int BC(int[][] colsum , int a , int c , int i){ int ssum = 0; if(a==0) ssum = colsum[c][i]; else ssum = colsum[c][i]-colsum[a-1][i]; return ssum; } //获取部分和的方法 public static int[][] GetColSum(int[][] arr){ int n = arr.length; //表示有n行 int m = arr[0].length; //表示有m列 int colsum[][] = new int[n][m]; //创建一个与arr大小完全相同的数组存放部分和 for(int i = 0 ; i < m ; i++){ colsum[0][i] = arr[0][i]; } for(int i = 1 ; i <n ; i++){ for(int j = 0 ; j < m ; j++){ colsum[i][j] = colsum[i-1][j]+arr[i][j]; } } return colsum; }
结果相同,但是部分和矩阵不同了。这里的部分和矩阵代表每一列的部分行之和。
如colsum[i][j]代表第j列元素从第0行加到第i行的和。
结果:
行数:4
列数:3
获得每列部分行之和:
1 -2 3
5 3 9
12 11 0
22 22 -12
最大值为:45
参考资料:《编程之美》

浙公网安备 33010602011771号