214. Shortest Palindrome(KMP)

本文地址:http://blog.csdn.net/freeelinux/article/details/53159929

Given a string S, you are allowed to convert it to a palindrome by adding characters in front of it. Find and return the shortest palindrome you can find by performing this transformation.

For example:

Given "aacecaaa", return "aaacecaaa".

Given "abcd", return "dcbabcd".


显然这是一个KMP算法的应用,在我上篇博客已经写过KMP算法了。在这里我们来分析一下:

如果某个字符串是回文的,那么s == reverse(s)。由此,我们可以将s逆转之后拼接在s后面,即ss = s + rev_s。该新字符串ss中首尾相同的部分,即

为s中以s[0]为起始地最长回文字串。

1.如: ss = abcd + dcba = abcddcba,显然在原字符串s中abcd最长回文子串长度就是a,(回文串就是正着读和倒着读都一样的串),所以我们需要给a前 面加上dcba,补齐其他不是回文串的字符,构成dcbabcd,那么dcb就可以通过后面的rev_s - 'a'(没有这种语法,只是为了说明)得到。然后再把dcb和原字符串s拼接即可。

2.再比如:s = aacecaaa,rev_s = aaacecaa,所以ss = aacecaaa + aaacecaa = aacecaaaaaacecaa,可得它的最长回文串是aacecaa,所以 rev_s - aacecaa(同上) = a,然后把a拼接在原字符串的前面即可。这可以通过KMP算法求得,可以参考我上篇博文。KMP算法记录了每个下标所对应的next值,

也就是每个下标前面的字符串前后缀完全相同的个数,同时也是下一步要比较元素的下标。

如图:




我们先忽略中部的#号,它的作用后面会说。

图中的next值由KMP算法即可求得,但是KMP算法求的是当前下标前面字符串相同的前后缀长度,比如下标16,next值为6,说明它前面字符串前后缀相同的最大长度是6,即图中的aaceca,不包括16自己。

那么问题来了,既然不包括自己,那我们怎么算新字符串前后缀的最长的长度呢?图中我们可以看出,结果应该为7,aacecaaa。

有这样一种思路,在新字符串后面加上#号,即添加17号元素,然后用KMP算法求17号元素next值,即可求得新字符串的前后缀相同的最长长度7,我们最后用rev_s的长度8-7=1,说明我们只需添加rev_s前部一个字符a,即可使原字符串构成一个回文串。

但是这种思路是有问题的!如果输入为aaaa呢,那么由我们的KMP算法可得,我们的新元素ss = s + revs = aaaaaaaa#, 按照KMP算法算得前后缀最大长度为7!注意,KMP算法所谓的前后缀长度是可以交叉的。因为它的判断条件if s[k] == s[j] ,next[++j] = ++k,所以只要前面的索引和后面索引对应的字符相等,那么最长前后缀长度即next值就会一直增加,所以得到7这个结果。

然而,rev_sz的长度仅为4,4-7=-3!我们后面用的substr函数在输入区间为负值的情况已,默认输出原函数。也就是说rev_sz.substr(0,-3)输出aaaa,在拼接上aaaa,就是aaaaaaaa了。虽然这仍是回文的,但是对于本来就是回文的字符串来说,做了不必要的运算。

针对aaaa问题,我们可以采取前面图中的方案,在两个字符串中间加个#,用来划分。

我的思路是这样的,先上代码:

class Solution {
public:
    string shortestPalindrome(string s) {
        string rev_sz = s;
        reverse(rev_sz.begin(), rev_sz.end());
        string ss = s + '#' + rev_sz;
        vector<int> next = get_next(ss, ss.size());
        return rev_sz.substr(0, rev_sz.size()-(next[ss.size()-1]+1)) + s;
    }   
private:
    vector<int> get_next(const string& ss, const int length){
        vector<int> next(length);
        next[0] = -1; 
        int j = 0, k = -1; 
        while(j < length -1){
            if(k == -1 || ss[k] == ss[j])
                next[++j] = ++k;
            else
                k = next[k];
        }   
        return next;
    }   
};


求next值不用说,但是由于是在中间加'#‘号,那么我们不能求得next[17]的next值,因为该元素不存在,当然你可以再加个’#'号,不过这就有点得不偿失了。我的方案是,用next[16]+1即可。上图代码已给出。

为什么呢?这是可以证明的。

//备注下面1这个证明不完整,可以跳到2

1.由于我数学水平不够,我采用半数学的证明方式。。。

例如如下图,字符串aacecaaa和它的reverse:




对任意字符串和它的逆串,如果有 ① == ②,①到③的长度和 ③到④的长度相同,且为逆串,可知② == ③,所以必有①==③,③==④。所以任意字符串和它的逆串符合该条件时,后面的③④位必然相等。

2.

对于任意字符串,逆序字符串的最后一位就是原来字符串的第一位,这是毫无疑问的。

从宏观来看,原字符串和逆序字符串只有两种情况:a代表首字符,b代表第二个字符,后面的情况忽略。

(1) s:  ab   rev_s:  ba,这种情况ss即两字符串相加得到的字符串,它的最后一位的next值必为0,如所以下一次匹配,由于逆序,a必然匹配,所以next[++j] = ++k,也可以说是前面的k+1即可。在上面例子中,这个值即为next[17]的值,说明此情况next[17] = next[16] + 1。

(2) s:  aa    rev_s:  aa,这种情况最后一位next值已经为1了,但同样由于逆序关系,最后一位必然匹配,所以next[++16] = next[17] = ++k = 2。

综上所述,一个字符串和它的逆序字符串匹配,假设字符串长度为len,那么必然有next[len] = next[len-1] + 1,得证 。


当然别人还有这样的思路,就是修改next[]数组的计算方法,next的计算方法改为计算当前包括自己在内的最长相同前后缀长度,代码是这样的:

class Solution {
public:
    string shortestPalindrome(string s) {
        string rev_s = s;
        reverse(rev_s.begin(), rev_s.end());
        string l = s + "#" + rev_s;
        
        vector<int> p(l.size(), 0);
        for (int i = 1; i < l.size(); i++) {
            int j = p[i - 1];
            while (j > 0 && l[i] != l[j])
                j = p[j - 1];
            p[i] = (j += l[i] == l[j]);
        }
        //std::cout<<p[l.size()-1]<<std::endl;       
        for_each(p.begin(), p.end(), [] (int i) {cout<<i<<' ';});
        return rev_s.substr(0, s.size() - p[l.size() - 1]) + s;
    }
};

                                                                             

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值