您的位置:首页 > 编程语言

[编程之美] PSet2.15 子数组之和的最大值(二维)

2014-08-09 22:16 197 查看
问题描述:

                          对于二维数组如何分析子数组之和的最大值呢?(想象一个大矩形中小矩形的元素之和)

解答与思路:

       解法一:暴力枚举

           枚举每一个矩形区域,然后求这个区域中元素的和,代码如下:

//方法一:暴力枚举所有矩形,求所有矩形元素和中的最大值
//矩阵大小m*n
int MaxSum(int **A , int m ,int n)
{
int maxSum = -9999;
for(int i_min=0 ; i_min<m ; i_min++)
for(int j_min=0 ; j_min<n ; j_min++)
for(int i_max=i_min ; i_max<m ; i_max++)
for(int j_max=j_min ; j_max<n ; j_max++)
maxSum = max(maxSum ,sum(A,i_min,i_max,j_min,j_max));
return maxSum;
}

       这种方法复杂度为O(N^2 * M^2 * Sum时间复杂度),对此Sum函数如果每次都采用最直接的遍历,时间复杂度太大了!考虑到求和操作很频繁,可以考虑“空间换时间”的方法保留“部分和”,需要O(N*M)作预处理,将计算结果保留,这样可以在O(1)时间内计算出任意一个区域的和。

       定义部分和PS[i][j]为以(0,0)、(i,j)连线为对角线的矩形元素之和,可以知道对于(i_min,i_max)、(j_min,j_max)连线为对角线矩阵元素之和可以通过PS[i_max][j_max] - PS[i_max][j_min-1] -PS[i_min-1][j_max] +PS[i_min-1][j_min-1],也就是在已知部分和PS的基础上通过O(1)计算出任意矩形元素之和。

       部分和PS可以通过划分为更小的子问题经过O(N*M)得到。考虑PS[i][j]=PS[i-1][j] + PS[i][j-1] - PS[i-1][j-1] + Arr[i][j],于是通过如下代码:

//方法二:暴力枚举所有矩形,求矩形元素和的最大值
//A矩阵大小m*n,部分和矩阵PS大小为(m+1)*(n+1),要保留一圈初始值,PS下标从1开始存放有用数据

void countPS(int (*PS)[5] , int m , int n , int (*A)[4])
{
//---边界条件
for(int i=0 ; i<m+1 ; i++)
PS[i][0] = 0;
for(int j=0 ; j<n+1 ; j++)
PS[0][j] = 0;
//---计算部分和PS
for(int i=1 ; i<m+1 ; i++)
for(int j=1 ; j<n+1 ; j++)
PS[i][j] = PS[i-1][j] + PS[i][j-1] - PS[i-1][j-1] + A[i-1][j-1];
}
int MaxSum(int (*A)[4] , int m ,int n , int (*PS)[5])
{
int maxSum = -9999;
int temp = 0;
countPS(PS,m,n,A);
for(int i_min=0 ; i_min<m ; i_min++)
for(int j_min=0 ; j_min<n ; j_min++)
for(int i_max=i_min ; i_max<m ; i_max++)
for(int j_max=j_min ; j_max<n ; j_max++){
temp = PS[i_max+1][j_max+1]-PS[i_max+1][j_min]-PS[i_min][j_max+1]+PS[i_min][j_min];
if(maxSum <temp)
maxSum = temp;
}
return maxSum;
}


        解法二:将二维问题转化为一维问题

         由于矩阵的连续性可以考虑将每一列中第a行和第c行之间的元素看做一个整体,这样也就是说对于第a行到第c行之间所有小矩形对应和,最大的为max(BC[1],BC[2],···,BC
),其中BC[i]=A[a][i]+······+A[c][i],如果再枚举所有上下边界(行边界),对于每一个特定上下边界转化为一维情况下求最大子数组和的问题。这种方法复杂度O(N^2*M)。代码如下:

//方法三:将二维数组问题转化为确定行边界后的一维情况
//A矩阵大小m*n,部分和矩阵PS大小为(m+1)*(n+1),要保留一圈初始值,PS下标从1开始存放有用数据

void countPS(int (*PS)[5] , int m , int n , int (*A)[4])
{
//---边界条件
for(int i=0 ; i<m+1 ; i++)
PS[i][0] = 0;
for(int j=0 ; j<n+1 ; j++)
PS[0][j] = 0;
//---计算部分和PS
for(int i=1 ; i<m+1 ; i++)
for(int j=1 ; j<n+1 ; j++)
PS[i][j] = PS[i-1][j] + PS[i][j-1] - PS[i-1][j-1] + A[i-1][j-1];
}
//计算第i行到第j行之间竖条和
int countBC(int i, int j, int m , int (*PS)[5])
{
return (PS[j+1][m+1]-PS[i][m+1]-PS[j+1][m]+PS[i][m]);
}
int MaxSum(int (*A)[4] , int m ,int n , int (*PS)[5])
{
int maxSum = -9999;
countPS(PS,m,n,A);
int Start , All;
for(int i=0 ; i<m ; i++)
for(int j=i ; j<m ; j++){
//---自底向上边界条件
Start = countBC(i,j,n,PS);
All = countBC(i,j,n,PS);
for(int k=n-1 ; k>=0 ; k--){
if(Start < 0)
Start=0;
Start += countBC(i,j,k,PS);
if(Start > All)
All = Start;
}
if(All > maxSum)
maxSum = All;
}

return maxSum;
}


扩展问题:如果二维数组也是首尾相连,像一条首尾相连的带子,算法如何改变?

         解答:类似一维情况的解法,只需要分为两种情况,一种是穿越左右界限的,一种是不穿越的(原问题),对于穿越左右界限的只需要求解某个子数组之和为负且绝对值最大的那一部分,然后用整个数组之和减去这部分就可以了。返回结果取所有情况中的最大即可。
int MaxSum(int (*A)[4] , int m ,int n , int (*PS)[5])
{
int maxSum = -9999;

countPS(PS,m,n,A);
int Start , All;
int minStart,minAll;
for(int i=0 ; i<m ; i++)
for(int j=i ; j<m ; j++){
//---自底向上求解不穿越左右边界问题(原问题)
Start = countBC(i,j,n,PS);
All = countBC(i,j,n,PS);
for(int k=n-1 ; k>=0 ; k--){
if(Start < 0)
Start=0;
Start += countBC(i,j,k,PS);
if(Start > All)
All = Start;
}
//---求解穿越左右边界问题,求子数组之和为负值,且绝对值最大
int sum = 0;
for(int k=0 ; k<n ; k++)
sum += countBC(i,j,k,PS);
minStart = countBC(i,j,0,PS);
minAll = countBC(i,j,0,PS);
for(int k=1 ; k<n ; k++){
if(minStart > 0)
minStart=0;
minStart += countBC(i,j,k,PS);
if(minStart < minAll)
minAll = minStart;
}
if(minAll < 0)
sum = sum-minAll;
All = All<sum?sum:All;

if(All > maxSum)
maxSum = All;
}

return maxSum;
}


内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息