【经典问题】最大子串和
2013-10-11 10:26
302 查看
最近几天好好的研究了一下这个问题。
问题本身就不多说了,求一串数字中的所有子串中,和最大的一个子串。例如:
输入:-10 5 2 3 4 -5 -23 3 7 -21
输出:14 5 4
一、各种方法
方法1:
这是最直接最暴力的方法,我没有写,时间复杂度为O(n3),明显这里面有很多重复的运算,我们可以很容易的把时间复杂度降为O(n2)。实际上,我第一印象就是下面这个
方法2:
这个方法理解为,以x[i]开头的所有子串中,和最大的一个。比枚举所有的 i 和 j 减少了计算量。下面一个比较重要的方法,虽然在时间复杂度上面没有提高,却包含了一个应对区间问题很重要的技巧。
方法3:
这个算法中,比较重要的一点事注意到子串和x[i...j] = cumarr[j] = cumarr[i-1],这个经验可以用在区间问题上。一个简单的列子:
n个收费站之间有n-1段路,每段路花费为P,用O(1)时间求任意两个收费站的之间的花费,要求空间为O(n)。
万能的二分能不能用到这个问题上,显然是可以的。在这个二分的过程中,需要注意的就是,合并结果的时候,需要注意到,跨左边跟右边的子串和的计算,最后就是在左边的最大子串、右边的最大子串、中间跨界的最大串这三者中取最大值。
方法4:
对于这个二分,还有一个待解决的问题,我想尝试一下,记录最大子串的起始位置和结束位置。个人对这种递归的理解确实不够,还没能够实现记录起始和结束位置。看来我还是得抽空好好把递归这个玩意儿好好理解一下。二分的话,显然时间复杂度为O(n*logn)。
接下来,就是O(n)的方法了,再来回顾一下方法3中,sum = cumarr[j] - cumarr[i-1]。要使得sum的值最大,显然cumarr[j]越大,cumarr[i-1]的值越小,sum的值越大。于是我们可以遍历cumarr数组,维持一个最小的cumarr[min_s],然后取cumarr[j]-cumarr[min_s]的最大值。
方法5:
这里还有一个方法6:
对于这个方法我要转载一个比较好的解释:
-10 1 2 3 4 -5 -23 3 7 -21 (num)
-10 | 1 3 6 10 8 | -23 | 3 10 | -21 (Sum)(|号为该处Sum被清零)
由于10是Sum的最大值,所以,红色的那块就是要求的范围了。
这样就比较好理解第六种方法。
二、思考问题:
1.证明最大子串和的时间复杂度下届是O(n)
各位如果有思路或者资料麻烦告诉我一声。。。无处下手啊
2.求子串和最接近0的子串。
嗯,对于这个问题,前面的经验有 sum = cumarr[j]-cumarr[i-1],这样的话问题就转换成,求cumarr数组里面差值最小或者相等的两个元素。
用排序的方法,再遍历一次数组就可以得出结果,时间复杂度为O(n*logn)。 有没有更好的方法?
3.求子串和最接近t的子串。
这个问题,如果继续采用问题二的方案,问题转换成,在一个排好序的数组里面找两个值的差值为t,显然不能达到同样的效果。暂时没有想到其他更好的方法。
4.m和n为整数,给定x
,求整数i,使得 x[i] + ... + x[i+m] 的和最接近为0。(即在第二个问题的基础上,加了条件: m个元素)
这个问题因为相差为m,扫一遍cumarr数组貌似就ok了。
想要透彻的理解一个算法真的很难,我感觉我还是不太会思考。上面大部分内容是参考编程珠玑上面的内容,看懂正文好像不是太难,但是后面的习题就各种傻了。也许是我看得太少的缘故吧。
接下来想要看得主题 column 4 writing correct programs
参考资料:
《programming Pearls》
http://blog.csdn.net/hcbbt/article/details/10454947
问题本身就不多说了,求一串数字中的所有子串中,和最大的一个子串。例如:
输入:-10 5 2 3 4 -5 -23 3 7 -21
输出:14 5 4
一、各种方法
方法1:
maxsofar = 0 for i = [0,n) for j = [i,n) sum = 0 for k =[i,j] sum += x[k] maxsofar = max(maxsofar,sum)
这是最直接最暴力的方法,我没有写,时间复杂度为O(n3),明显这里面有很多重复的运算,我们可以很容易的把时间复杂度降为O(n2)。实际上,我第一印象就是下面这个
方法2:
int s,e; int maxsum = -999999; int tempsum = 0; for(int i=0;i<K;i++) { tempsum = 0; for(int j=i;j<K;j++) { tempsum+=input[j]; if(maxsum < tempsum) { maxsum = tempsum; s = i; e = j; } } }
这个方法理解为,以x[i]开头的所有子串中,和最大的一个。比枚举所有的 i 和 j 减少了计算量。下面一个比较重要的方法,虽然在时间复杂度上面没有提高,却包含了一个应对区间问题很重要的技巧。
方法3:
cumarr[-1] = 0 for i = [0,n) cumarr[i] = cumarr[i-1] + x[i] maxsofar = 0 for i = [0,n) for j = [i,n) sum = cumarr[j] - cumarr[i-1] maxsofar = max(maxsofar,sum)
这个算法中,比较重要的一点事注意到子串和x[i...j] = cumarr[j] = cumarr[i-1],这个经验可以用在区间问题上。一个简单的列子:
n个收费站之间有n-1段路,每段路花费为P,用O(1)时间求任意两个收费站的之间的花费,要求空间为O(n)。
万能的二分能不能用到这个问题上,显然是可以的。在这个二分的过程中,需要注意的就是,合并结果的时候,需要注意到,跨左边跟右边的子串和的计算,最后就是在左边的最大子串、右边的最大子串、中间跨界的最大串这三者中取最大值。
方法4:
int max_sub(int m,int n) { if(m > n) return 0; if(m == n) return max(0,input[m]); int k = (m+n)/2; //中间部分的结果显然是由以x[k]结尾的最大子串和 //和以x[k+1]开头的最大子串和 相加而得 int lmax,sum,a; lmax = sum = a = 0; for(int i=k;i>=m;i--) { sum += input[i]; if(lmax < sum) { lmax = sum; a = i; } } int rmax,b; rmax = sum = b = 0; for(int i=k+1;i<=n;i++) { sum += input[i]; if(rmax < sum) { rmax = sum; b = i; } } int max_l = max_sub(m,k); int max_r = max_sub(k+1,n); int result = max(lmax+rmax,max(max_l,max_r)); /* how to record the start and the end */ return result; }
对于这个二分,还有一个待解决的问题,我想尝试一下,记录最大子串的起始位置和结束位置。个人对这种递归的理解确实不够,还没能够实现记录起始和结束位置。看来我还是得抽空好好把递归这个玩意儿好好理解一下。二分的话,显然时间复杂度为O(n*logn)。
接下来,就是O(n)的方法了,再来回顾一下方法3中,sum = cumarr[j] - cumarr[i-1]。要使得sum的值最大,显然cumarr[j]越大,cumarr[i-1]的值越小,sum的值越大。于是我们可以遍历cumarr数组,维持一个最小的cumarr[min_s],然后取cumarr[j]-cumarr[min_s]的最大值。
方法5:
min_s = -1//这里要初始化为-1 cumarr[-1] = 0; for(int i=0;i<K;i++) cumarr[i] = cumarr[i-1] + input[i]; for(int i=0;i<K;i++) { sum = cumarr[i] - cumarr[min_s]; if(maxsofar < sum) { maxsofar = sum; s = min_s+1; e = i; } if(cumarr[min_s] > cumarr[i]) min_s = i; }
这里还有一个方法6:
int start = 0; for(int i=0;i<K;i++) { if(maxendinghere+input[i]>0) { maxendinghere += input[i]; } else if(maxendinghere + input[i] <= 0) { maxendinghere = 0; start = i + 1; } if(maxendinghere > maxsofar) { maxsofar = maxendinghere; e = i; s = start; } }
对于这个方法我要转载一个比较好的解释:
-10 1 2 3 4 -5 -23 3 7 -21 (num)
-10 | 1 3 6 10 8 | -23 | 3 10 | -21 (Sum)(|号为该处Sum被清零)
由于10是Sum的最大值,所以,红色的那块就是要求的范围了。
这样就比较好理解第六种方法。
二、思考问题:
1.证明最大子串和的时间复杂度下届是O(n)
各位如果有思路或者资料麻烦告诉我一声。。。无处下手啊
2.求子串和最接近0的子串。
嗯,对于这个问题,前面的经验有 sum = cumarr[j]-cumarr[i-1],这样的话问题就转换成,求cumarr数组里面差值最小或者相等的两个元素。
用排序的方法,再遍历一次数组就可以得出结果,时间复杂度为O(n*logn)。 有没有更好的方法?
3.求子串和最接近t的子串。
这个问题,如果继续采用问题二的方案,问题转换成,在一个排好序的数组里面找两个值的差值为t,显然不能达到同样的效果。暂时没有想到其他更好的方法。
4.m和n为整数,给定x
,求整数i,使得 x[i] + ... + x[i+m] 的和最接近为0。(即在第二个问题的基础上,加了条件: m个元素)
这个问题因为相差为m,扫一遍cumarr数组貌似就ok了。
min = max_num cumarr[-1] = 0 for i = [0,n) if(i+m<n) temp = cumarr[i+m]-cumarr[i-1] if (min > temp) min = temp start = i end = i+m
想要透彻的理解一个算法真的很难,我感觉我还是不太会思考。上面大部分内容是参考编程珠玑上面的内容,看懂正文好像不是太难,但是后面的习题就各种傻了。也许是我看得太少的缘故吧。
接下来想要看得主题 column 4 writing correct programs
参考资料:
《programming Pearls》
http://blog.csdn.net/hcbbt/article/details/10454947
相关文章推荐
- 【黑金原创教程】【Modelsim】Modelsim原创教程连载导读【连载完成,共六章】
- 【黑金原创教程】【TimeQuest】TimeQuest原创教程连载导读【连载完成,共七章】
- IOS屏幕方向的判断
- 如何使用HttpURLConnection发起这http请求
- rhel 配置vnc
- 如何清除网络共享文件夹的记忆帐号和密码
- win7 64位 开机密码忘记
- 黑马程序员--java技术--基础部分
- Oracle sql语句执行顺序
- C++的函数重载
- 向模拟器的sdcard中添加文件
- TF101出现“DMClient已停止”处理办法
- 当Table中td内容为空时,让它显示边框的办法
- JDBC连接数据库过程(转载)
- 插入排序
- dialog样式的activity的全屏设置
- WinCE自启动学习2
- busybox详解
- spring中velocity的配置
- spring 在web项目中的一些基本配置