zoukankan      html  css  js  c++  java
  • 5. Longest Palindromic Substring

    题目:

    Given a string S, find the longest palindromic substring in S. You may assume that the maximum length of S is 1000, and there exists one unique longest palindromic substring.

    链接:https://leetcode.com/problems/longest-palindromic-substring/

    题解:

    卡在这道题很久,直接导致没有继续刷题的动力。这道题有许多种解法,下面分别来看一看最简单的中心展开法, Manacher算法,后缀树suffix tree,以及使用Rabin-carp rolling hash方法

    1) 中心展开法:

    从头遍历数组,考虑Palindrome是奇数和偶数两种情况。  Time Complexity - O(n2),  Space Complexity - O(1)

    public class Solution {
        public String longestPalindrome(String s) {     // O(n * n)  - go from middle
            if(s == null || s.length() == 0)
                return "";
            String res = s.substring(0, 1);
            
            for(int i = 0; i < s.length(); i++) {
                String tmp = getSubstring(s, i, i);
                if(tmp.length() > res.length()) 
                    res = tmp;
                tmp = getSubstring(s, i, i + 1);
                if(tmp.length() > res.length()) 
                    res = tmp;
            }        
            return res;
        }
        
        private String getSubstring(String s, int lo, int hi) {
            while(lo >= 0 && hi <= s.length() - 1 && s.charAt(lo) == s.charAt(hi)) {
                    lo--;
                    hi++;
            }
            return s.substring(lo + 1, hi);
        }
    }

    2) Manacher's Algorithm

    非常天才的方法,充分利用了Palindrome的特性,代码绝大部分使用了Sedgewick教授的。           Time Complexity - O(n), Space Complexity - O(n)

    1. 先对原字符串等距离插入'#',可以略去奇偶Panlindrome的判定
    2. 以当前center中心设置 i 的mirror,假如 i 仍然在当前center最长Palindrome的右边界范围内,依据Palindrome的特性, p[i] = Math.min(right - i, p[mirror])
    3. 以i为中心进行扩展,计算出p[i] - 当前的最长Palindrome
    4. 假如以i为中心的最长Palindrome超过了右边界,更新i和right
    public class Solution {              //mostly coded by Sedgewick
        private int[] p; 
        private String s;
        private char[] t;                //transformed string
        
        public String longestPalindrome(String s) {
            this.s = s;
            preprocess();
            p = new int[t.length];
            
            int center = 0, right = 0;        
            for(int i = 1; i < t.length - 1; i++) {
                int mirror = 2 * center - i;
                if(right > i)                       
                    p[i] = Math.min(right - i, p[mirror]);
                    
                while(i - p[i] >= 0 && i + p[i] < p.length && t[i + p[i]] == t[i - p[i]])     //try to expand
                    p[i]++;
                
                if(i + p[i] > right){
                    center = i;
                    right = i + p[i];
                }
            }
            
            int maxLength = 0;   
            center = 0;   
            
            for (int i = 1; i < p.length-1; i++) { // i is center,  p[i] is half length of longest Palindrome in t
                if (p[i] > maxLength) {
                    maxLength = p[i];
                    center = i;
                }
            }
            
            return s.substring((center - p[center] + 2) / 2, (center + p[center]) / 2); 
        }
        
        private void preprocess() {                 //preprocess to avoid old/even length
            t = new char[s.length() * 2 + 1];
            for(int i = 0; i < s.length(); i++) {
                t[2 * i] = '#';
                t[2 * i + 1] = s.charAt(i);
            }
    
            t[t.length - 1] = '#';
        }
    }

    3) Suffix Tree

    后缀树/后缀词典/后缀数组应该是这类问题的终极解决方法了。对于这个问题后缀树不如Manacher有效率。不过我们还是来看一看怎么解决这个问题。 (待补充)

    4) Rabin-carp, rolling hash

    二刷:

    Java:   Manacher:  Time Complexity - O(n), Space Complexity - O(n)  - 1/3/2016

    二刷的时候仍然被这算法卡了几天。今天再次尝试叙述一下逻辑:

    1. preprocess,将输入字符串s等间距插入特殊字符'#',目的是为了以后计算方便,不用太多考虑长度的奇偶性,不preprocess其实也可以
    2. char[] t代表经过preprocess以后的transformed string
    3. int[] p, p[i]是length of longest Palindromic string centerred at i,就是以i为中心,最长Palindromic string的长度
    4. mirror代表以当前的center为中心,坐标i在center左边的镜像。因为 i - center = center - mirror,所以mirror = 2 * center - i。
    5. 下面比较重要的一点就是Manacher's Algorithm最主要的性质, 这里利用了之前计算过的最长的Palindromic String。假设之前计算过一个很长很长的Palindromic String,其中心在center, 那么我们遍历当前center的后面的元素 i 的时候,假如这个i在之前那个很长的Palindromic String的右边界范围内,那么我们就可以得到一个公式来节约重新匹配 - p[i] = Math.min(p[mirror], right - i), 即 p[i] 的当前最小值等于 p[mirror] 和 right - i这两个值中的较小者。 这里p[mirror]是以这个mirror为中心的最长回文字符串的长度。
      1. 就比如"#a#b#a#a#b#a#",假如当前的center在两个"a"中间的"#",假设我们正计算以之后那个"a"为中心的结果, 因为之前的p[mirror]等于2,所以这里p[i]至少等于2和right - i中的较小者。
      2. 假如我们正计算第二个"b"为中心的结果,因为之前的p[mirror]等于4,所以p[i]至少等于4和right - i中的较小者。
      3. 有了p[mirror]和right - i这两个边界,我们就可以节约许多的重复matching
    6. 上一步确定了p[i]的最小值以后,我们就可以继续进行尝试扩展当前Palindromic。这个时候我们没有取巧的办法,只能在t中对t[i + p[i]]和t[i - p[i]]进行逐字符对比,假如他们相同,则我们增加p[i]的长度,直到求出以i为中心的最长回文串长度
    7. 在上一步找出了以i为中心的最长回文串长度后,我们比较一下当前字符串的右边界是否大于历史最长回文串右边界"right",假如当前回文串更长,则我们更新center = i,  新的右边界right = p[i] + i。
    8. 这里我们就完成了p[i]的计算,也就是我们得到了每个以i为中心的最长回文字符串的长度。 这个时候我们再遍历一遍数组,找到其中最长的那一个,然后再对原始字符串进行substring操作就可以了
    public class Solution {
        public String longestPalindrome(String s) {
            if (s == null || s.length() == 0) {
                return s;
            }
            char[] t = new char[s.length() * 2 + 1];    // transformed string
            int[] p = new int[t.length];      // p[i] is length of longest Palindromic string centered at i
            preprocess(s, t);
            int center = 0, right = 0;
            
            for (int i = 1; i < t.length - 1; i++) {
                int mirror = 2 * center - i;    //   center - mirror = i - center,  mirror is i's mirror based on center
                if (i < right) {                // if i within pre-calculated palindrome boundary
                    p[i] = Math.min(p[mirror], right - i);  //then p[i] is at least p[mirror]
                }                                                            // or right - i
                while (i + p[i] < t.length && i - p[i] >= 0 && t[i + p[i]] == t[i - p[i]]) {   // try to expand current Palindrome
                        p[i]++;    
                }
                if (i + p[i] > right) {// if new palindromic string right boundary > old boundary, update center and right boundary
                    center = i;
                    right = i + p[i];
                }
            }
            
            center = 0;
            int maxLen = 0;
            for (int i = 1; i < p.length - 1; i++) {
                if (p[i] > maxLen) {
                    center = i;
                    maxLen = p[i];
                }
            }
            
            return s.substring((center - p[center] + 2) / 2, (center + p[center]) / 2);
        }
        
        private void preprocess(String s, char[] t) {
            for (int i = 0; i < s.length(); i++) {
                t[2 * i] = '#';
                t[2 * i + 1] = s.charAt(i);
            }
            t[t.length - 1] = '#';
        }
    }

    Python:

    class Solution(object):
        def longestPalindrome(self, s):
            """
            :type s: str
            :rtype: str
            """
            t = '#'
            for char in s:                  # preprocess
                t += char + '#'
            p = [0] * len(t)                # p[i] is longest Palindromic string centered at i
            center = right = 0
            for i in range(1, len(t) - 1):
                mirror = 2 * center - i     # mirror is mirror of i centered at center,  center - mirror = i - center
                if i < right:           # if i within range of lps, i is at least min(p[mirror], right - i)
                    p[i] = min(p[mirror], right - i)    
                while i + p[i] < len(t) and i - p[i] >= 0 and t[i - p[i]] == t[i + p[i]]:   # try expand current palindromic string
                    p[i] += 1
                if i + p[i] > right:                    # try update right
                    center = i
                    right = i + p[i]
            center = 0
            maxLen = 0
            for i in range(1, len(t) - 1):          # find the longest one
                if p[i] > maxLen:
                    center = i
                    maxLen = p[i]
            return s[(center - p[center] + 2) / 2 : (center + p[center]) / 2]                        

    Reference:

    http://articles.leetcode.com/2011/11/longest-palindromic-substring-part-ii.html

    http://algs4.cs.princeton.edu/home/

    http://en.wikipedia.org/wiki/Longest_palindromic_substring

    http://www.felix021.com/blog/read.php?2040                         <- 中文

    http://stackoverflow.com/questions/10468208/manachers-algorithm-algorithm-to-find-longest-palindrome-substring-in-linear-t

  • 相关阅读:
    如何学习一门新技术
    linux atoi
    linux switch 跳转到 ”跳转至 case 标号“ 的错误
    from unittest import TestCase
    ensure that both new and old access_token values are available within five minutes, so that third-party services are smoothly transitioned.
    .BigInteger
    408
    Convert a string into an ArrayBuffer
    Optimal asymmetric encryption padding 最优非对称加密填充(OAEP)
    https://tools.ietf.org/html/rfc8017
  • 原文地址:https://www.cnblogs.com/yrbbest/p/4430093.html
Copyright © 2011-2022 走看看