hiho 1 最长回文子串
2015-10-23 15:08
253 查看
问题描述:
一个字符串中连续的一段就是这个字符串的子串,而回文串指的是”12421”这种从前往后读和从后往前读一模一样的字符串,所以最长回文子串的意思就是这个字符串中最长的身为回文串的子串啦~思路:
最笨的方法是:枚举字符串的每个位置,求出以当前位置为中心的回文子串的长度。这个方法有两个问题:
1.只考虑了所有的长度为奇数的回文子串。
2.每次枚举的复杂度为O(n), 总体复杂度为O(n2)
解法:
处理长度为偶数的子串其实只要将所有的字符用特殊字符隔开,就可以用上面求奇数的算法处理偶数长度的情况了。
例如原始字符串为 “abaaba”,
预处理后为”#a#b#a#a#b#a#”
按照上面方法当枚举到第7个字符#时可以得到最长的回文子串(#a#b#a#a#b#a#),然后去掉所有的# 就是答案(abaaba)。
O(n) 解法
1 中已经将答案为偶数的情况转换为奇数的求解,下面只考虑答案为奇数的情况。
笨方法中其实是有重复处理的,考虑下面的例子:
序号 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
s(i) | a | b | a | b | a | b | a | c |
f(i) | 0 | 1 | 2 | 3 | 2 | ? |
现在要求f(6) = ?,笨方法求解时就要比较s(5)和s(7)是否相等,但是我们可以发现在求4号的时候已经读入了 s(5)和s(7),也就是说s(5)和s(7)信息已经包含在了之前的 f(i) 中,如果我们能从之前的f(i)中判断出 s(5)=s(7),那么就可得出f(6) >= 1 的结论。
下面就来看看其中隐藏的关系:
由f(4) = 3可知
s(7) = s(1),s(5) = s(3)
要判断s(7) ? s(5) 等价于判断 s(1) ? s(3)
而f(2) =1,说明
s(1) = s(3)
所以 s(7) = s(5) 因此f(6)≥ 1
我们来总结一下上面的方法,首先我们选择了一个辅助点 j ,这个辅助点的选择条件是
使得f(j) + j 最大的j , 这意味着此j 的回文串右边界最大,也就是说在求f(j) 时读取了更靠右边的字符,这样 f(j)包含了更多靠后的字符信息。
辅助点j 的使用方法如下图所示
图中绿色表示辅助点j 的最长回文串, i_mirror 是指以j为中心i的对称点
对于关于i对称的两个点 A 和 B 要判断 s(A) ? s(B) 我们可以将A B 以j 为中心找到其对应的对称点A’B’
由于A和A’ 在j 的回文串中 且关于j 对称
因此 s(A) = s(A’) 同理 s(B) = s(B’)
现在转换为要判断s(A’) ? s(B’)
A’ 和 B’ 是关于 i_mirror 对称的两个点(想想为什么),如果A’ 和 B’ 在i_mirror 的回文串中,就有
s(A’) = s(B’)
而这个判断等价于
f(i_mirror) ≥ A’ - i_mirror = i - A = B - i
=> B-i ≤ f(i_mirror) …………………….(1)
同时要保证 B和 B’在j 的最大回文子串中
f(j) + j ≥ B
=> f(i)+j -i ≥ B-i
=> B-i ≤ f(i) +j -i ……………….. (2)
由(1), (2)两式可得:
B -i ≤ min(f(i_mirror), f(j) + j - i)
取最大的B-i作为f(i) 的初始值
f(i) = min(f(i_mirror), f(j) + j - i) …………….(3)
这样就得到了一个较大的f(i) 然后再向两边扩展。
最后上代码
实现上的小技巧:
1. 在字符串的两端分别加上^ 和 $ 这样就不用考虑边界情况了。
2. 在加入# 后,求出的f() 值恰好为回文子串的长度。
3. 注意公式3 中f(i)可能< 0。
#include<cstdio> #include<cstring> #include<cmath> #include <algorithm> enum{maxn = 2000000+4}; int n; char s[maxn], str[maxn]; int f[maxn]; #define OJ int main() { #ifndef OJ freopen("in.txt", "r", stdin); #endif // OJ scanf("%d", &n); while(n){ n--; scanf("%s", str); int t = 0; s[t++] = '^'; s[t++] = '#'; int k=0; while(str[k]){ s[t++] = str[k++]; s[t++] = '#'; } s[t++] = '$'; s[t++] = '\0'; int ans = 0; f[1] = 0; int j = 1; for (int i=2; i < t-1; i++){ int i_mirror = 2*j -i; f[i] = std::min(f[i_mirror], f[j] + j -i); f[i] = std::max(0, f[i]); while(s[i+f[i]] == s[i-f[i]]) f[i]++; f[i]--; if (f[i] + i > f[j] +j) { j = i; } ans = std::max(ans, f[i]); } printf("%d\n", ans); } return 0; }
复杂度分析:
首先如果公式3中求得的f(i_mirror) != f(j) + j - i 那么while(s[i+f[i]] == s[i-f[i]]) f[i]++; 将只执行一次(想想为什么)
如果f(i_mirror) == f(j) + j - i 那么 在i 处会继续扩展,while(s[i+f[i]] == s[i-f[i]]) f[i]++;将从
i+ f[i] = i+ f[j] + j -i = f[j] + j 处开始, 而f[j] + j 就是最当前读入的最右边字符,直到不相等为止。循环开始时f[i] + i = f[j] + j , 循环结束后有 f[i] + i ≥ f[j] + j; 此时i成为下一个j。下次又从当前位置开始比较。所以比较的次数就是max(f(j) + j)= O(n)。
总体复杂度为O(n)。
相关文章推荐
- 数据库链接字符串查询网站
- Flex字符串比较 还有Flex字符串操作
- Ruby中创建字符串的一些技巧小结
- ASP下经常用的字符串等函数参考资料
- 将字符串小写转大写并延时输出的批处理代码
- 将字符串转换成System.Drawing.Color类型的方法
- Lua源码中字符串类型的实现
- Lua性能优化技巧(四):关于字符串
- 字符串聚合函数(去除重复值)
- Ruby中的字符串编写示例
- 总结的5个C#字符串操作方法分享
- sqlserver中求字符串中汉字的个数的sql语句
- sql server字符串非空判断实现方法
- VBS的字符串及日期操作相关函数
- C#实现将千分位字符串转换成数字的方法
- jquery 删除字符串最后一个字符的方法解析
- PowerShell实现在字符串中查找大写字母
- PowerShell中使用Out-String命令把对象转换成字符串输出的例子
- PowerShell中字符串使用单引号和双引号的区别
- Powershell小技巧之获取字符串的行数