你須要的LeeCode題No.05——「最長迴文子串」_一點課堂(多岸學院)

最長迴文子串

題目:最長迴文子串java

描述:給定一個字符串 s,找到 s 中最長的迴文子串。你能夠假設 s 的最大長度爲 1000。算法

示例:編程

  • 輸入: "babad"
  • 輸出: "bab"
  • 注意: "aba" 也是一個有效答案。

解析

迴文字符串可能咱們不少人在學習編程語言基礎時都練習過,可是隻是判斷了一個字符串是不是迴文的而已,而今天的題目要複雜得多了。若是咱們要找出全部的子串,再一一判斷它是否是迴文的,可能須要O(n<sup>3</sup>)的時間複雜度,因此咱們要對它進行優化。下面咱們要介紹的兩種方法,能夠將此問題的時間複雜度分別下降到O(n<sup>2</sup>)和O(n)。數組

方法一:中心擴展

迴文字符串的特性就是中心對稱,那麼咱們能夠假定每一個字符都是一個迴文子串的中心字符,而後以此字符爲中心向兩側擴展,直到再也不是迴文子串爲止。這樣咱們就找到了以每一個字符爲中心的最長迴文子串,也就等於找到了最長的迴文子串。不過這裏有一點須要注意,那就是一個迴文串的中心多是一個字符,例如"aba"就是以字符 'b' 爲中心,也多是在兩個字符之間,例如"abba"的中心就在兩個 'b' 字符之間,只要處理好這兩種狀況,代碼就很簡單了,參考以下:app

public String longestPalindrome(String s) {
    int len = s.length();
    if(len==0)return "";

    int i = 0;
    int[] ends = new int[2];
    
    while(i<len){
        // 處理 "aba" 的狀況
        getEnds(s, len, i-1, i+1, ends);
        // 處理 "abba" 的狀況
        getEnds(s, len, i, i+1, ends);
        i++;
    }

    return s.substring(ends[0], ends[1]);
}

private void getEnds(String s, int len, int left, int right, int[] ends){
    while(left >= 0 && right < len && s.charAt(left)==s.charAt(right)){
        left--;
        right++;
    }
    if((ends[1]-ends[0])<(right-left-1)){
        ends[0] = left+1;
        ends[1] = right;
    }
}

方法二:Manacher算法

這個方法也稱爲「馬拉車」算法,能夠說是解決迴文字符串最好的算法了,由於它的時間複雜度僅爲O(n)。下面咱們一步步分析這個算法是如何作到如此高效的。編程語言

首先,咱們用 '#' 或某個字符串中不存在的字符將字符串的每一個字符分隔開,並在首尾也插入一個 '#',以下所示:函數

file

以上操做解決了由於長度的奇偶性帶來的問題,新的字符串長度是 2*n+1,是始終爲奇數的。學習

接下來和中心擴展同樣,咱們也要肯定以一個字符爲中心的最長迴文子串的長度。Manacher算法中有一個概念叫做「迴文半徑」,它指的是從中心字符到最右側(或最左側)字符的距離,例如"#a#"的半徑就是2,。用此距離構造一個半徑迴文數組radius,咱們根據數組中的最大值就能夠肯定最長迴文子串的長度了。例如,以上示例中間的字符 'b' 的迴文半徑爲4,構成的迴文數組以下:優化

file

能夠發現,以原串第 i 個字符爲中心的最長迴文子串,就是radius數組第 i 位的值減 1 。那麼,咱們如何構造這個數組呢?ui

假設咱們已經求得了位置 i 處(相對於Manacher構造的字符串)的迴文半徑,也就是radius[i]的值,並記它的最右側位置爲 mx,以下所示:

file

而後咱們要計算位置 j 處的迴文半徑,其中 j>i。因爲radius[i]的值可能爲0,也可能較大,因此 j 可能小於 mx,也可能大於 mx,咱們先來看 j<=mx 時的狀況。以下所示,j<mx 時,j 關於 i 的對稱點 i-(j-i)(咱們記爲 k),也必定大於 mx關於 i 的對稱點 i-(mx-i)(咱們記爲mn),以下所示:

file

如今,讓咱們把目光聚焦到 j 的對稱點 k 上,它將爲咱們計算 j 的迴文半徑提供線索。由於radius[k]的值可能很小,以 k 爲中心的迴文子串徹底被以 i 爲中心的子串覆蓋,也可能較大超出了 i 的範圍,以下所示:

file file

對於第一種狀況,能夠確定 j 也將被 i 徹底覆蓋,因此它的迴文半徑和 k 是相等的。第二種狀況則代表從 mx 以後的部分是未知的,須要進一步計算。

如今咱們再來考慮 j>=mx 的狀況,以下圖所示,這時 j 的迴文半徑一點都沒計算過,因此只能進一步進行計算。

file

根據以上思路,能夠參考的代碼以下:

public String longestPalindromeOptimize(String s) {
    if (s == null || s.length() == 0) {
        return "";
    }

    char[] charArr = manacherStr(s);
    // 構造迴文半徑數組
    int[] radius = new int[charArr.length];
    // 當前已經計算過的最右側下標
    int mx = -1;
    // i 表示 mx 最大時,對應的迴文子串的中心
    int i = -1;
    // 最長迴文子串的長度
    int max = 0;
    // 最長迴文子串的起點下標
    int maxIndex = -1;

    for (int j = 0; j < radius.length; j++) {
        // 2*i-j 是 j 相對於 i 的對稱點,也就是文章中的 k。
        // 當 j<mx 時,因爲以 k 爲中心的子串可能被 i 徹底覆蓋,也可能超出 i 的範圍
        // 因此,計算 咱們只能計算出 radius[j] 在 mx-j這個範圍內的值
        // 而當 j>mx 時,就須要徹底從頭開始計算了
        radius[j] = j < mx ? Math.min(radius[2 * i - j], mx - j + 1) : 1;

        // 計算 j<mx 時超出 mx-j 範圍內的部分,或者 j 本就大於 mx時
        while (j + radius[j] < charArr.length && j - radius[j] >= 0
                && charArr[j - radius[j]] == charArr[j + radius[j]]) {
            radius[j]++;
        }

        // 更新 mx 的值和 i 的值
        if (j+radius[j]>mx) {
            mx = j+radius[j]-1;
            i = j;
        }

        // 更新max的值
        if (max<radius[j]) {
            max = radius[j];
            // j 的起點座標,相對於Manacher字符數組而言是 j-radius[j]+1
            // 而相對於原數組而言,這個值是由原數組的位置乘以2再加一獲得的,因此直接除以2便可
            maxIndex = (j-radius[j]+1)/2;
        }
    }

    return s.substring(maxIndex,maxIndex+max-1);
}

private char[] manacherStr(String s){
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < s.length(); i++) {
        sb.append('#');
        sb.append(s.charAt(i));
    }
    sb.append('#');
    return sb.toString().toCharArray();
}

總結

Manacher算法若是咱們沒有聽過仍是很難想到的,不過不要氣餒,咱們只是來學習的,想不到能學到也算賺到,可以理解它也算一筆不小的收穫。下面這個題目較爲簡單,讓咱們放鬆一下吧。

下題預告

題目:Z 字形變換

描述:將一個給定字符串根據給定的行數,以從上往下、從左到右進行 Z 字形排列。

好比輸入字符串爲 "LEETCODEISHIRING" 行數爲 3 時,排列以下:

L   C   I   R
    E T O E S I I G
    E   D   H   N

以後,你的輸出須要從左往右逐行讀取,產生出一個新的字符串,好比:"LCIRETOESIIGEDHN"。

請你實現這個將字符串進行指定行數變換的函數:string convert(string s, int numRows);

示例 1:

  • 輸入: s = "LEETCODEISHIRING", numRows = 3
  • 輸出: "LCIRETOESIIGEDHN"

示例 2:

  • 輸入: 輸入: s = "LEETCODEISHIRING", numRows = 4
  • 輸出: "LDREOEIIECIHNTSG"
  • 解釋:
L     D     R
    E   O E   I I
    E C   I H   N
    T     S     G

相關源碼請加QQ獲取。


【感謝您能看完,若是可以幫到您,麻煩點個贊~】

更多經驗技術歡迎前來共同窗習交流: 一點課堂-爲夢想而奮鬥的在線學習平臺 http://www.yidiankt.com/

![關注公衆號,回覆「1」免費領取-【java核心知識點】] file

QQ討論羣:616683098

QQ:3184402434

想要深刻學習的同窗們能夠加我QQ一塊兒學習討論~還有全套資源分享,經驗探討,等你哦! 在這裏插入圖片描述

相關文章
相關標籤/搜索