[Leetcode] Longest Palindromic Substring 最長迴文子字符串

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.

暴力法 Brute Force

複雜度

時間 O(n^3) 空間 O(1)php

思路

暴力法就是窮舉全部子字符串的可能,而後依次按位判斷其是不是迴文,並更新結果。雖然其時間複雜度很高,但它對空間的要求很低。java

代碼

public class Solution {
    public String longestPalindrome(String s) {
        int maxLength = 0;
        int maxStart = 0;
        int len = s.length();
        //i是字符串長度
        for(int i = 0; i < len; i++){
            //j是字符串起始位置
            for(int j = 0; j < len - i; j++){
                //挨個判斷是否迴文
                if(isPalindrome(s,i,j) && (i+1)>maxLength){
                    maxLength = i + 1;
                    maxStart = j;
                }
            }
        }
        return s.substring(maxStart,maxStart + maxLength);
    }
    
    private isPalindrome(String s, int i, int j){
        int left = j;
        int right = j + i;
        while(left<right){
            if(s.charAt(left)!=s.charAt(right)){
                return false;
            }
            left++;
            right--;
        }
        return true;
    }
}

動態規劃 Dynamic Programming

複雜度

時間 O(n^2) 空間 O(n^2)python

思路

根據迴文的特性,一個大回文按比例縮小後的字符串也一定是迴文,好比ABCCBA,那BCCB確定也是迴文。因此咱們能夠根據動態規劃的兩個特色:第一大問題拆解爲小問題,第二重複利用以前的計算結果,來解答這道題。那如何劃分小問題呢,咱們能夠先把全部長度最短爲1的子字符串計算出來,根據起始位置從左向右,這些一定是迴文。而後計算全部長度爲2的子字符串,再根據起始位置從左向右。到長度爲3的時候,咱們就能夠利用上次的計算結果:若是中心對稱的短字符串不是迴文,那長字符串也不是,若是短字符串是迴文,那就要看長字符串兩頭是否同樣。這樣,一直到長度最大的子字符串,咱們就把整個字符串集窮舉完了,可是因爲使用動態規劃,使計算時間從O(N^3)減小到O(n^2)。算法

注意

  • 外循環的變量控制的實際上不是字符串長度,而是字符串首到尾的增量
  • 二維數組的第一維是指子字符串起始位置,第二維是指終止位置,所存數據表示是否迴文

代碼

public class Solution {
    public String longestPalindrome(String s) {
        int maxLength = 0;
        int maxStart = 0;
        int len = s.length();
        boolean[][] dp = new boolean[len][len];
        //i是字符串長度
        for(int i = 0; i < len; i++){
            //j是字符串起始位置
            for(int j = 0; j < len - i; j++){
                if(i==0||i==1){
                    //若是字符串長度爲0,一定爲迴文
                    dp[j][j+i] = true;
                } else if(s.charAt(j+i)==s.charAt(j)){
                    //若是左右兩端相等,那隻要中心對稱子字符串是迴文就是迴文
                    dp[j][j+i] = dp[j+1][j+i-1];
                } else {
                    //不然不是迴文
                    dp[j][j+i] = false;
                }
                if(dp[j][j+i] && i > maxLength){
                    maxLength = i + 1;
                    maxStart = j;
                }
            }
        }
        return s.substring(maxStart,maxStart + maxLength);
    }
}

中心擴散法 Spread From Center

複雜度

時間 O(n^2) 空間 O(1)數組

思路

動態規劃雖然優化了時間,但也浪費了空間。實際上咱們並不須要一直存儲全部子字符串的迴文狀況,咱們須要知道的只是中心對稱的較小一層是不是迴文。因此若是咱們從小到大連續以某點爲箇中心的全部子字符串進行計算,就能省略這個空間。 這種解法中,外層循環遍歷的是子字符串的中心點,內層循環則是從中心擴散,一旦不是迴文就再也不計算其餘以此爲中心的較大的字符串。因爲中心對稱有兩種狀況,一是奇數個字母以某個字母對稱,而是偶數個字母以兩個字母中間爲對稱,因此咱們要分別計算這兩種對稱狀況。app

代碼

public class Solution {
    String longest = "";
    
    public String longestPalindrome(String s) {
        for(int i = 0; i < s.length(); i++){
            //計算奇數子字符串
            helper(s, i, 0);
            //計算偶數子字符串
            helper(s, i, 1);
        }
        return longest;
    }
    
    private void helper(String s, int idx, int offset){
        int left = idx;
        int right = idx + offset;
        while(left>=0 && right<s.length() && s.charAt(left)==s.charAt(right)){
            left--;
            right++;
        }
        // 截出當前最長的子串
        String currLongest = s.substring(left + 1, right);
        // 判斷是否比全局最長還長
        if(currLongest.length() > longest.length()){
            longest = currLongest;
        }
    }
}

2018/2優化

class Solution:
    def longestPalindrome(self, s):
        """
        :type s: str
        :rtype: str
        """
        maxStr = ''
        for index in range(0, len(s)):
            sub1 = self.spreadFromCenter(s, index, 0)
            sub2 = self.spreadFromCenter(s, index, 1)
            if len(sub1) > len(maxStr):
                maxStr = sub1
            if len(sub2) > len(maxStr):
                maxStr = sub2
        return maxStr
        
    def spreadFromCenter(self, string, centerIndex, offset):
        leftIndex = centerIndex
        rightIndex = centerIndex + offset
        length = len(string)
        while leftIndex >= 0 and rightIndex < length and string[leftIndex] == string[rightIndex]:
            leftIndex = leftIndex - 1
            rightIndex = rightIndex + 1
        leftIndex = leftIndex + 1
        substring = string[leftIndex:rightIndex]
        return substring

馬拉車算法 Manacher Algorithm

複雜度

時間 O(n) 空間 O(n)ui

關於時間複雜度的證實:http://www.zhihu.com/question...code

思路

Manacher算法是很是經典的計算連續下標迴文的算法。它利用了迴文的對稱性,更具體的來講,是迴文內迴文的對稱性,來解決這個問題。
參見:http://www.felix021.com/blog/...blog

代碼

public class Solution {
    public String longestPalindrome(String s) {
        if(s.length()<=1){
            return s;
        }
        // 預處理字符串,避免奇偶問題
        String str = preProcess(s);
        // idx是當前可以向右延伸的最遠的迴文串中心點,隨着迭代而更新
        // max是當前最長迴文串在總字符串中所能延伸到的最右端的位置
        // maxIdx是當前已知的最長迴文串中心點
        // maxSpan是當前已知的最長迴文串向左或向右能延伸的長度
        int idx = 0, max = 0;
        int maxIdx = 0;
        int maxSpan = 0;
        int[] p = new int[str.length()];
        for(int curr = 1; curr < str.length(); curr++){
            // 找出當前下標相對於idx的對稱點
            int symmetryOfCurr = 2 * idx - curr;
            // 若是當前已知延伸的最右端大於當前下標,咱們能夠用對稱點的P值,不然記爲1等待檢查
            p[curr] = max > curr? Math.min(p[symmetryOfCurr], max - curr):1;
            // 檢查並更新當前下標爲中心的迴文串最遠延伸的長度
            while((curr+p[curr])<str.length() && str.charAt(curr+p[curr])==str.charAt(curr-p[curr])){
                p[curr]++;
            }
            // 檢查並更新當前已知可以延伸最遠的迴文串信息
            if(curr+p[curr]>max){
                max = p[curr] + curr;
                idx = curr;
            }
            // 檢查並更新當前已知的最長迴文串信息
            if(p[curr]>maxSpan){
                maxSpan = p[curr];
                maxIdx = curr;
            }
        }
        //去除佔位符
        return s.substring((maxIdx-maxSpan)/2,(maxSpan+maxIdx)/2-1);
    }
    
    private String preProcess(String s){
        // 如ABC,變爲$#A#B#C#
        StringBuilder sb = new StringBuilder();
        sb.append("$");
        for(int i = 0; i < s.length(); i++){
            sb.append("#");
            sb.append(s.charAt(i));
        }
        sb.append("#");
        return sb.toString();
    }
}

後續 Follow Up

Q:若是隻能在頭或尾刪,問最少刪多少字符能使得該字符串變爲迴文?A:就是找到最長迴文串,而後把長度減一下就好了。

相關文章
相關標籤/搜索