字串最大和求解
http://acm.hdu.edu.cn/showproblem.php?pid=1003
ACM的一道题。之前在《算法导论》中看到介绍寻找最大字串的方法。书中介绍了一种用递归来求解的方法。另外提出可以最O(n)时间内找到最大和字串。今天主要说下自己的思路。
1、暴力计算方法。
如字符串: 5 6 -1 5 4 -7
以5开头,要计算(5)(5,6)(5,6,-1)(5,6,-1,5)(5,6,-1,5,4)(5,6,-1,5,4,-7)这6个字串的最大值。
以6开头,计算5个,同理类推,对于输入为n,计算的字串个数为 n(n+1)/2,所以复杂度是O(n*n).
2、O(n)求最大值的方法。
基本原理:对于一个已经知道最大子串(last)的字符串(A)(注:记字符串为A,最大字串为last)。加入一个新元素cur,形成新的字符串B。此时考虑B的最大子串。
已知:A的最大子串为last,B=A+cur (在A的末尾加上cur,形成新的字符串B)
求: B的最大子串。
当我们从字符串的左边第一个元素开始应用程序时,输入的字符串为S
第一个元素: A = S[0], last = S[0]
添加新元素S[1]: B = A + cur = S[0] + S[1].
我们需要考虑last(最大子串)的更新规律。观察由A到B过程中,last的更新。
用pos_b表示最大子串last的起始位置,pos_e表示最大子串last的终止位置
1、将cur与last连接起来,求出一个候选值candidate1。由于last与cur中间可能不是连续的。
例如: A = (5,6,-1), 可知 last = 11,pos_e = 1, pos_e = 2
加入cur = 5. 则 B = (5,6,-1,5), 如果要把 cur 和 last 连接起来,则需要记录中间的元素,如 -1
定义 need1 表示 cur 和 last 的中间元素,可以得到
need1 += cur;
candidate1 = last + need1
2、第二个候选值(可能成为最大子串的连续子串)为cur和之前的和为整数组成的子串。这个比较难理解。看下面的例子。
5 -6 3 7
(由1中计算的candidate1 = 9)
最大子串为(3,7)所以处理 cur = 7 时,需要去考虑cur之前的元素是否最以cur为结尾的子串有贡献(与1中不同的是,此时cur不一定与last连接起来。)
定义 need2 表示cur之前元素对cur结尾子串的贡献。可知只有当need2为正数时,有贡献,为0或负数时舍去。
if(need2 > 0)
need2 += cur; //need2有贡献
else
need2 = cur; //无贡献,则将need2设置为cur,表示抛弃之前的数据。
所以可得2分析的候选子串candidate2 = need2
3、第三个候选子串为last,没有加入cur之前的子串。如果cur<0, 则 B中最大的子串依然是A中的最大子串last. candidate3 = last.
所以。最大字串的更新,last = max(candidate1,candidate2,candidate3).
源代码如下:
注意每次更新完最大值后,需要要更新记录最大子串的区间的下标 pos_b, pos_e
可能有多重解。控制 candidate1,candidate2,candidate3 相等时,如何更新最大子串的下标,可以得到不同的解。(最长、最短、最靠左、最靠右等。)
#include <iostream> using namespace std; //已知前一个字串的最大和last,加入当前cur,可能的情况如下 // 1、last <= 0, 放弃last, 则cur或cur之前的元素和为当前字串最大值 // 2、last > 0, cur < 0; 将last作为当前最大值,如果cur > 0 // 需要考虑将cur连接上,或cur之前部分的和 // 注:字符最短时、最前 // if(last >= can1 && last >= can2) // else if(can1 > last && can1 > can2) // else if(can2 > last && can2 >= can1) // 字符最长时,最前 // if(last > can1 && last >= can2) // else if(can1 >= last && can1 >= can2) // else if(can2 > last && can2 > can1) // can1最长,can2和last长短需要另外计算,can2靠后,last靠前 int _tmain(int argc, _TCHAR* argv[]) { //freopen("a.in","r",stdin); //freopen("a.out","w",stdout); int last,cur,need1,need2; int can1,can2; int seq,num; int pos_b,pos_e,pos_h; cin>>seq; for(int i = 1; i <= seq; i++) { cin>>num; cin>>cur; last = cur; need1 = 0; need2 = 0; pos_b = 1; pos_e = 1,pos_h = 1; for(int j = 2; j <= num; j++) { cin>>cur; need1 += cur; if(need2 + cur > cur) { need2 += cur; } else { need2 = cur; pos_h = j; } can1 = last + need1; can2 = need2; //update if(last > can1 && last >= can2) //最短 { //nothing to update } else if(can1 >= last && can1 >= can2) { need1 = 0; need2 = 0; last = can1; pos_e = j; } else if(can2 > last && can2 > can1) //更新最大值和下标 { need1 = 0; need2 = 0; last = can2; pos_b = pos_h; pos_e = j; } } if(i == seq) { cout<<"Case "<<i<<":"<<endl; cout<<last<<" "<<pos_b<<" "<<pos_e<<endl; } else { cout<<"Case "<<i<<":"<<endl; cout<<last<<" "<<pos_b<<" "<<pos_e<<endl<<endl; } } return 0; }