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

问题描述:

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

解答与思路:

       解法一:暴力枚举

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

//方法一:暴力枚举所有矩形,求所有矩形元素和中的最大值
//矩阵大小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[n]),其中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;
}



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值