leetcode 214. Shortest Palindrome 最短回文数 + KMP算法

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算法,但是我暂时搞不懂为什么这么做,这道题先这么办。

建议和leetcode 680. Valid Palindrome II 去除一个字符的回文字符串判断 + 双指针 一起学习

代码如下:


/*
 * KMP算法是一种专门用来匹配字符串的高效的算法,
 * 具体方法可以参见这篇博文从头到尾彻底理解KMP。
 * 我们把s和其转置r连接起来,中间加上一个其他字符,
 * 形成一个新的字符串t,我们还需要一个和t长度相同的一位数组next,
 * 其中next[i]表示从t[i]到开头的子串的相同前缀后缀的个数,
 * 具体可参考KMP算法中解释。最后我们把不相同的个数对应的字符串添加到s之前即可,
 * 
 * http://www.cnblogs.com/grandyang/p/4523624.html
 * http://blog.csdn.net/tingting256/article/details/50454924
 * */
class Solution 
{
    public String shortestPalindrome(String s) 
    {
        String tmp=s+"#"+new StringBuilder(s).reverse().toString();
        System.out.println(tmp);

        int[] table=getTable(tmp);
        return new StringBuilder(s.substring(table[table.length-1])).reverse().toString()+s;         
    }

    //KMP算法
    public int[] getTable(String s)
    {
         int len=s.length();
         int[] table=new int[len];
         char[] a=s.toCharArray();
         //i是从1开始
         for(int i=1,k=0;iwhile(k>0&&a[i]!=a[k])
                k=table[k-1];
            if(a[i]==a[k])
                k++;
             table[i]=k;
         }
         return table;
    }


    /*
     * 这是最直接的方法,但是超时,这个方法是寻找开始的最长回文序列,然后在做添加
     * */
    public String shortestPalindromeByLoop(String s)
    {
        if(s==null || s.length()<=1 || isPalindrom(s))
            return s;

        int i=s.length()-1;
        for(;i>=1;i--)
        {
            if(isPalindrom(s.substring(0,i)))
                break;
        }
        StringBuilder builder=new StringBuilder(s.substring(i)).reverse();
        s=builder.toString()+s;
        return s;
    }
    boolean isPalindrom(String s)
    {
        int i=0,j=s.length()-1;
        while(iif(s.charAt(i)==s.charAt(j))
            {
                i++;
                j--;
            }else 
                return false;
        }
        return true;
    }
}

下面是C++的做法,

让在前面补一些字符使得给定的字符串变成回文,观察可以发现我们需要添加多少个字符与给定字符串的前缀子串回文的长度有关.也就是说去掉其前缀的回文子串,我们只需要补充剩下的子串的逆序就行了,举个栗子:aacecaaa,其前缀的最大回文子串是aacecaa,剩下了一个a,因此我们只需要在前面加上一个a的逆序a就行了.再例如abcd,其前缀的最大回文是a,因此剩下的子串就为bcd,所以需要在前面加上bcd的逆序,就变成了dcbabcd.所以这样问题就转化为求字符串的前缀最大回文长度.

OK,思路是这样,接下来就要看怎么实现了,一个naive的方法是先判断整个字符串是否回文,否的话再判断前n-1个子串是否回文,这样依次缩减长度,直到找到一个回文子串就是最大的前缀回文子串.这种方法简单粗暴,容易理解和实现,如果在面试中要求不是很严格的情况下说不定可以过关, 反正总比不会好点^.^.其时间复杂度是O(n!).

当然问题不会这么简单,接下来的才是真正有效的解法,KMP.这是一个非常高效的字符串比较算法.其原理是给定一个字符串S和P,要在S中寻找是否存在P,一般的方法是逐位比较,如果不能完全匹配,则S再回到开始位置向右移动一位,P回到0位置再开始比较.在KMP中不需要回到首部重新开始比较,借助与记录从P的开头到当前位置P中的前缀和后缀有多少位是相等的,这样当P和S比较的时候如果P[i] != S[j]了,不需要回到P[0]的位置重新比较,我们可以查看P中已经匹配过的子串中(也就是P[0, i-1]子串)前缀和后缀有多少位是相等的,然后将P的前缀和已经S[j]之前的后缀是匹配的,就可以不用回溯S了.

代码如下:

#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

using namespace std;


class Solution 
{
public:
    string shortestPalindrome(string s) 
    {
        if (s.length() <= 1)
            return s;

        string tmp = s;
        reverse(tmp.begin(), tmp.end());
        string st = s + "#" + tmp;
        vector<int> next = calaNext(st);

        string left = s.substr(next.back()+1);
        reverse(left.begin(), left.end());
        return left + s;
    }

    vector<int> calaNext(string s)
    {
        vector<int> next(s.length(), 0);
        next[0] = -1;
        int k = -1;
        for (int i = 1; i < s.length(); i++)
        {
            while (k > -1 && s[k + 1] != s[i])
                k = next[k];
            if (s[k + 1] == s[i])
                k += 1;
            next[i] = k;
        }
        return next;
    }
};

你可能感兴趣的:(leetcode,For,Java,需要好好想一下的题目,leetcode,For,C++)