两种解法代码 (KMP、Manacher) 214.最短回文串

参考自:https://leetcode-cn.com/problems/shortest-palindrome/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by–44/


214.最短回文串

给定一个字符串 s,你可以通过在字符串前面添加字符将其转换为回文串。找到并返回可以用这种方式转换的最短回文串。

来源:LeetCode
网址:https://leetcode-cn.com/problems/shortest-palindrome/

算法一:马拉车算法
模板:

/**
 * 预处理
 * @param s:原字符串
 * @return:返回预处理结果
 */
private String preProcess(String s){
    int n = s.length();
    if (n == 0)
        return "^$";
    String ans = "^";
    for (int i = 0; i < n; i++) {
        ans += "#" + s.charAt(i);
    }
    ans += "#$";
    return ans;
}

/**O(n)
 * Manacher
 */
private String Manacher(String s){
    String str = preProcess(s);//预处理
    int n = str.length();
    int[] p = new int[n];
    int center = 0,L = 0,R = 0;
    int mirror;
    for (int i = 1; i < n-1; i++) {
        mirror = 2*center- i;//i的镜像位置
        if (i < R){//当i小于R时
            p[i] = Math.min(R-i,p[mirror]);//如果p[mirror]没有越界的,则p[i]=p[mirror],否则还需要继续中心扩散
        }else p[i] = 0;

        while (str.charAt(i+p[i]+1) == str.charAt(i-p[i]-1)){//中心扩散
            p[i]++;
        }

        if (i+p[i] > R){//如果超出右边界,更新center和R
            center = i;
            R = i + p[i];
            L = i - p[i];
        }
    }

    //找出P的最大值(最长回文子串的半径)
    int maxLen = 0;
    int start = 0;
    for (int i = 1; i < n-1; i++) {
        if (p[i] > maxLen){
            maxLen = p[i];
            start = (i-p[i])/2;
        }
    }

    return s.substring(start,start+maxLen);
}

本题的应用:

思想:

  1. 设原字符串为:s = "abbacd"
  2. 通过适当修改Manacher算法,获得包含第一个字符的最长回文子串 pal = "abba"spal多了剩下的s2 = "cd"
  3. 也就是说正是s2造成了s不是回文串,那么接下来就很清楚了,将s2取逆得到s3 = "dc",将s3接到s的前面得到ans = s3 + s = "dcabbacd"
public String shortestPalindrome(String s){
    String pal = Manacher(s);
    return new StringBuilder(s.substring(pal.length())).reverse()+s;
}

private String preProcess(String s){
    int n = s.length();
    if (n == 0)
        return "^$";
    String ans = "^";
    for (int i = 0; i < n; i++) {
        ans += "#" + s.charAt(i);
    }
    ans += "#$";
    return ans;
}

private String Manacher(String s){
    String str = preProcess(s);//预处理
    int n = str.length();
    int[] p = new int[n];
    int center = 0,L = 0,R = 0;
    int mirror;
    for (int i = 1; i < n-1; i++) {
        mirror = 2*center- i;//i的镜像位置
        if (i < R){//当i小于R时
            p[i] = Math.min(R-i,p[mirror]);//如果p[mirror]没有越界的,则p[i]=p[mirror],否则还需要继续中心扩散
        }else p[i] = 0;

        while (str.charAt(i+p[i]+1) == str.charAt(i-p[i]-1)){//中心扩散
            p[i]++;
        }

        if (i+p[i] > R){//如果超出右边界,更新center和R
            center = i;
            R = i + p[i];
            L = i - p[i];
        }
    }

    //找出P的最大值(最长回文子串的半径)
    //针对本题,这里需要修改一下,该回文串必须是从头开始的
    int maxLen = 0;
    int start = 0;
    int tmp = 0;
    for (int i = 1; i < n-1; i++) {
        tmp = (i-p[i])/2;
        if (p[i] > maxLen && tmp == 0){//从头开始的
            maxLen = p[i];
            start = tmp;
        }
    }

    return s.substring(start,start+maxLen);
}

算法二:KMP
模板:

private static int KMP(String text,String pattern){
        int[] next = getNext(pattern);
        for (int i = 0,j = 0; i < text.length(); i++) {
            while (j > 0 && text.charAt(i) != pattern.charAt(j))
                j = next[j-1];

            if (text.charAt(i) == pattern.charAt(j))
                j++;

            if (j == pattern.length())
                return i - j + 1;
        }
        return -1;
    }
    private static int[] getNext(String pat){
        int[] next = new int[pat.length()];
        next[0] = 0;
        for (int i = 1,j = 0; i < pat.length(); i++) {
            while (j > 0 && pat.charAt(i) != pat.charAt(j))
                j = next[j-1];

            if (pat.charAt(i) == pat.charAt(j))
                j++;

            next[i] = j;
        }
        return next;
    }

本题的应用:

要想了解KMP最重要的就是了解next数组干了些什么?
这里呢,我也不讲什么前缀和后缀了,想了解更多点击这里
.
现在你只需要知道,若 next[j] = k,则 s[0 ~ k-1] = s[j-k ~ j-1]
举个栗子,s = "abcecaba"那么next={0,0, 0, 0, 0, 1, 2, 1};
next[7] = 2, 根据定义得 s[0:1]=s[5:6],即 “abcecaba”,
所以,你可以把next[]看成是字符串s的部分匹配数组,而next[j]中存的数值就是以下标j为终止的最长部分匹配长度
·
所以,现在再来看看这题,假设s = "aacecaaa",
我们定睛一看,发现s1 = s[0:6] = "aacecaa"是回文子串,还剩下了s2 = s[7] = "a",如果我们在s前面再补上s[7],就可以得到我们要找的最短回文串ans = s2 + s = "aaacecaaa";
·
此时,如果我们对 ss = s + "#" +s' = "aacecaaa#aaacecaa"(其中s’为s的逆串)求它的部分匹配数组next={0, 1, 0, 0, 0, 1, 2, 2, 0, 1, 2, 2, 3, 4, 5, 6, 7},分析一下,就可以发现next[16]=7,即"aacecaaa#aaacecaa"
.
s1 = ss[0:next[16]-1] = s[0:end-1] = "aacecaa",end=next[16],那么剩下的s2 = s[end:] = "a",所以ans = s2 + s =s.substring(next[ss.length()-1]) + s = aaacecaaa

public String shortestPalindrome(String s){
    String ss = s + "#" + new StringBuilder(s).reverse();
    int add = getLast(ss);
    return new StringBuilder(s.substring(add)).reverse()+s;
}

private int getLast(String s){//KMP的next数组,求最长前后缀
    //若 next[j] = k,则 w[0 ~ k-1] = w[j-k ~ j-1]
    int n = s.length();
    int[] next = new int[n];
    for(int i = 1,j = 0;i < n;i++){
        while (j > 0 && s.charAt(i) != s.charAt(j))
            j = next[j-1];

        if (s.charAt(i) == s.charAt(j))
            j++;

        next[i] = j;
        //System.out.print(next[i]+" ");
    }


    return next[n-1];
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值