最大子数组和(DP和递归解法)与最大子矩阵和
2015-03-19 15:43
162 查看
1. 最大子数组和
参考资料:/article/1386223.html
http://blog.csdn.net/beiyeqingteng/article/details/7056687
题目:输入一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如:当输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7,2,因此输出为该子数组的和18。
当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新定位赋值,不然的话这个负数将会减少接下来的和。
最大子数组和实际上是DP问题。点击打开链接
感性理解:
设b[i]为以第i个元素结尾(这是为了好推算递推式)且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么要求以第i个元素结尾且和最大的连续子数组,实际上要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即b[i] = max(b[i-1] + a[i], a[i])。判断b[i-1]
+ a[i]是否大于a[i]实际上等价于判断b[i-1]是否大于0。
注意:有时候,虽然b[i] = b[i-1] + a[i],但可能b[i] < b[i-1] ,所以这和最大上升子序列不同,要求得最大和子序列,必须进行max{b[i], 0=<i<n}的运算。
换句话说,b递减( 随着i的增大 )是可以容忍的,因为可以通过max来选择大者。但是一旦b小于0就无法容忍了,必须另起炉灶(b=a[i]),因为此时是不可跨越的,无法和a[i-1]连成连续数组,原因正是1)
1)当以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)小于0时,如果把这个负数和第i个数相加,得到的结果反而比第i个数本身还要小,所以这种情况下最大子数组和是第i个数本身。
2)如果以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)大于0,与第i个数累加就得到了以第i个数结尾的子数组中所有数字的和。
由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。
2.最大子矩阵和
第0行时,有0,0~1,0~2,...,0~m-1这m种情况(这些情况都是一一叠加的),每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
同样地,第1行时,就有1,1~2,...,1~m-1这m-1种情况,每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
...
第m-1行时,只有m-1这一种情况。
这实际上在求,第0行,第0行和第1行压缩成的一维数组,第0、1、2行压缩成的一维数组...第0、1、2、...、m-1行压缩成的一维数组;第1行,第1行和第2行压缩成的一维数组,第1、2、3行压缩成的一维数组...第1、2、3、...、m-1行压缩成的一维数组;......;第m-1行 。这所有的情况压缩成的一位数组的子数组和的最大值。
3. 最大子数组和的递归解法
出自weiss的书P20
最大子序列和可能在三处出现。其一,完全在数据的左半部分,其二,完全在数据的右半部分,其三,跨越左右——可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而相加得到。 最大子序列和是这三者的最大者。
另外值得注意的是,递归过程调用的一般形式是传递(整个)数组以及左边界和右边界,它们界定了数组要被处理的部分,也就是说,数组是作为完整的参数传递给函数的,但是真正需要处理的部分由边界值确定。
代码如下:
上面的代码中有一个递归出口比较难理解,可用下面的例子加以说明
参考资料:/article/1386223.html
http://blog.csdn.net/beiyeqingteng/article/details/7056687
题目:输入一个整型数组,数组里有正数也有负数。数组中连续的一个或多个整数组成一个子数组,每个子数组都有一个和。求所有子数组的和的最大值。要求时间复杂度为O(n)。
例如:当输入的数组为1, -2, 3, 10, -4, 7, 2, -5,和最大的子数组为3, 10, -4, 7,2,因此输出为该子数组的和18。
当我们加上一个正数时,和会增加;当我们加上一个负数时,和会减少。如果当前得到的和是个负数,那么这个和在接下来的累加中应该抛弃并重新定位赋值,不然的话这个负数将会减少接下来的和。
最大子数组和实际上是DP问题。点击打开链接
感性理解:
设b[i]为以第i个元素结尾(这是为了好推算递推式)且和最大的连续子数组。假设对于元素i,所有以它前面的元素结尾的子数组的长度都已经求得,那么要求以第i个元素结尾且和最大的连续子数组,实际上要么是以第i-1个元素结尾且和最大的连续子数组加上这个元素,要么是只包含第i个元素,即b[i] = max(b[i-1] + a[i], a[i])。判断b[i-1]
+ a[i]是否大于a[i]实际上等价于判断b[i-1]是否大于0。
注意:有时候,虽然b[i] = b[i-1] + a[i],但可能b[i] < b[i-1] ,所以这和最大上升子序列不同,要求得最大和子序列,必须进行max{b[i], 0=<i<n}的运算。
换句话说,b递减( 随着i的增大 )是可以容忍的,因为可以通过max来选择大者。但是一旦b小于0就无法容忍了,必须另起炉灶(b=a[i]),因为此时是不可跨越的,无法和a[i-1]连成连续数组,原因正是1)
1)当以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)小于0时,如果把这个负数和第i个数相加,得到的结果反而比第i个数本身还要小,所以这种情况下最大子数组和是第i个数本身。
2)如果以第(i-1)个数字为结尾的子数组中所有数字的和b(i-1)大于0,与第i个数累加就得到了以第i个数结尾的子数组中所有数字的和。
由于每次运算只需要前一次的结果,因此并不需要像普通的动态规划那样保留之前所有的计算结果,只需要保留上一次的即可,因此算法的时间和空间复杂度都很小。
int MaxSub_array( int n ,int a[] ) { int i; int max,b; max=b=a[0]; //赋的值必须是首项值,不能赋0,要不然如果数组全为负数的话,则求的最终结果就是0(max)了 for(i=1;i<n;i++) { if(b<=0) { sum=a[i]; } else { b+=a[i]; } if(b>max) { max=b; } } return max; }
2.最大子矩阵和
第0行时,有0,0~1,0~2,...,0~m-1这m种情况(这些情况都是一一叠加的),每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
同样地,第1行时,就有1,1~2,...,1~m-1这m-1种情况,每种情况都可以合并(压缩)成一维数组通过最大子数组和来求;
...
第m-1行时,只有m-1这一种情况。
这实际上在求,第0行,第0行和第1行压缩成的一维数组,第0、1、2行压缩成的一维数组...第0、1、2、...、m-1行压缩成的一维数组;第1行,第1行和第2行压缩成的一维数组,第1、2、3行压缩成的一维数组...第1、2、3、...、m-1行压缩成的一维数组;......;第m-1行 。这所有的情况压缩成的一位数组的子数组和的最大值。
int Max_SubMatrix(int *a[], int m, int n) { int sum=-10000; for(int i=0;i<m;i++){ int b ={0}; for(int j=i;j<m;j++){ for(int k=0;k<n;k++){ b[k]+=a[j][k]; } int max=Max_Subarray(n,b) if(max>sum){ sum=max; } } } return sum; }
3. 最大子数组和的递归解法
出自weiss的书P20
最大子序列和可能在三处出现。其一,完全在数据的左半部分,其二,完全在数据的右半部分,其三,跨越左右——可以通过求出前半部分的最大和(包含前半部分的最后一个元素)以及后半部分的最大和(包含后半部分的第一个元素)而相加得到。 最大子序列和是这三者的最大者。
另外值得注意的是,递归过程调用的一般形式是传递(整个)数组以及左边界和右边界,它们界定了数组要被处理的部分,也就是说,数组是作为完整的参数传递给函数的,但是真正需要处理的部分由边界值确定。
代码如下:
int Max_Subarray(int *a, int left, int right) { if (left==right){ return a[left]; } int mid=(right+left)/2; int Left_Max = Max_Subarray(a, left, mid); int Right_Max= Max_Subarray(a, mid+1, right); int Left_BorderSum=0; int Left_BorderMax=-65536; //负无穷 for(int i=mid;i>=left;i--){ Left_BorderSum+=a[i] if(Left_BorderSum>Left_BorderMax){ Left_BorderMax=Left_BorderSum; } } int Right_BorderSum=0; int Right_BorderMax=-65536; //负无穷 for(int j=mid+1;j<=right;j++){ //注意必须等于right,在最初调用的是Max_Subarray(a,0,n-1) ,是n-1,不是n Right_BorderSum+=a[i] if(Right_BorderSum>Right_BorderMax){ Right_BorderMax=Right_BorderSum; } } if( Left_Max>Right_Max && Left_Max>(Left_BorderMax+Right_BorderMax)) { return Left_Max; } if( Right_Max>Left_Max && Right_Max>(Left_BorderMax+Right_BorderMax)) { return Right_Max; } else return Left_BorderMax+Right_BorderMax; } int maxsubsequencesum(const a[],int n) { return Max_Subarray(a,0,n-1); }
上面的代码中有一个递归出口比较难理解,可用下面的例子加以说明
void printout( unsigned int n) { if(n>=10) printout(n/10); getchar(n%10+'0'); //只有n<10时,才能执行到这一句,递归才能返回,即隐性的递归出口——基准(base case) }在这段代码中,递归出口是隐性的,可以改成如下的显性形式:
void printout( unsigned int n) { if(n<10) getchar(n%10+'0'); //显性的出口,即基准情况(base case) else //else必须有,否则n<10时也要执行下面的语句,就死循环了。 printout(n/10); }
相关文章推荐
- 求数组中的最大偶数(递归解法)
- 数组中最大子矩阵,最简便的解法
- 51nod 1049 1050 1051 (循环数组)最大子段(子矩阵)和(dp)
- 数组中最大子矩阵,最简便的解法
- 51Nod 最大子矩阵和 | DP
- 杭电1081 暴力dp 最大子矩阵和
- dp求解最大子数组和
- 【HDU 1024】Max Sum Plus Plus(DP+滚动数组优化+最大m段字段之和)
- hdu 2870 最大子矩阵 单调队列优化+dp
- Java 递归求数组最大元素
- 最大子数组问题的线性解法-wikipedia
- 最大子矩阵和(n^2*m,dp,前缀和)
- 子数组的最大乘积(编程之美2.13,两种解法)
- 使用递归调用求数组的最大值,了解递归的栈调用以及递归函数的具体执行过程
- ACM模板——DP求解最长子段 最大子矩阵
- 从给定数组中找出最大的两个数——二分递归
- 最大公约数的递归解法
- [BZOJ 1084] SCOI 2005 最大子矩阵 · 简单DP
- 【简单DP】POJ 1050 最大子矩阵
- [51nod] 1050 循环数组最大子段和 dp