hiho 1 最长回文子串

来源:互联网 发布:淘宝直通车是淘宝的吗 编辑:程序博客网 时间:2024/05/21 01:43

问题描述:

http://hihocoder.com/problemset/problem/1032

思路:

最笨的方法是:枚举字符串的每个位置,求出以当前位置为中心的回文子串的长度。
这个方法有两个问题:
1.只考虑了所有的长度为奇数的回文子串。
2.每次枚举的复杂度为O(n), 总体复杂度为O(n2)

解法:

  1. 处理长度为偶数的子串

    其实只要将所有的字符用特殊字符隔开,就可以用上面求奇数的算法处理偶数长度的情况了。
    例如原始字符串为 “abaaba”,
    预处理后为”#a#b#a#a#b#a#”
    按照上面方法当枚举到第7个字符#时可以得到最长的回文子串(#a#b#a#a#b#a#),然后去掉所有的# 就是答案(abaaba)。

  2. 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(i)表示以当前位置 i 为中心的最长回文子串边界到中心 i 的距离,例如i = 4时最长回文子串为 abababa, 右边界为s(7) = ‘a’, 所以f(4) = 7-4 = 3。
    现在要求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 OJint 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[0] = f[1] = 0;        int j = 1;        for (int i=2; i < t-1; i++){            int i_mirror = 2*j -i; // i_mirror >= 0            // 因为i至少为0;            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)。

0 0
原创粉丝点击