部分文章内容为公开资料查询整理,原文出处可能未标注,如有侵权,请联系我,谢谢。邮箱地址:gnivor@163.com ►►►需要气球么?请点击我吧!

编程之美--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

 

参考资料:《编程之美》

posted @ 2015-06-18 22:08  流了个火  阅读(146)  评论(0)    收藏  举报
►►►需要气球么?请点击我吧!►►►
View My Stats