1. 程式人生 > >如何找到字串中的最長迴文子串?

如何找到字串中的最長迴文子串?

小史: 只要先對比第一個字元和倒數第一個字元,再對比第二個字元和倒數第二個字元,以此類推。如果都相等,那就是迴文串了。

  • 題目:給你一個字串,找出裡面最長的迴文子串。 例如,輸入abcdcef,那麼輸出應該是cdc; 輸入adaelele,輸出應該是elele。

小史: 可以遍歷整個字串,把每個字元和字元間的空隙當作迴文的中心,然後向兩邊擴充套件來找到最長迴文串。小史這次搶著分析時間和空間複雜度。

解題思路

小史回到學校,把面試情況和呂老師說了一下。

呂老師: 比如cabadabae用中心擴充套件的演算法,我已經知道了第三位為中心的aba和第5位為中心的abadaba是迴文,那麼在判斷第7位為中心的迴文串的時候,有什麼已知資訊嗎?

小史: 已知第5位為中心的abadaba是迴文,由迴文的特性,就能夠知道2-4位和6-8位對稱,而又知道第3位為中心的aba是迴文,所以2-4位是迴文。這樣的話,6-8位肯定是迴文。

小史拿著筆在紙上畫了半天,突然大叫一聲。

小史: 由於之前的計算,已經知道了第5位為中心的abadaba是迴文,而第4位為中心的a的迴文長度是1,所以第6位為中心的迴文長度只能是1,不用再去擴充套件判斷了。

小史: 以第7位為中心的迴文串的計算,由之前分析已經知道最小長度是3了,但是還是需要進行擴充套件,因為第9位是什麼,根據之前的資訊無法得知,需要擴充套件進行探索。

小史: 而以第6位為中心的迴文串的計算,並不需要進行探索了,因為根據之前第5位為迴文中心串的資訊、和第4位為迴文中心串的資訊,已經可以推斷,第6位為迴文中心串的長度只能為1。

小史: 當然可以。

  • 1、首先,我們要記錄下目前已知的迴文串能夠覆蓋到的最右邊的地方,就像案例中的第8位
  • 2、同時,覆蓋到最右邊的迴文串所對應的迴文中心也要記錄,就像案例中的第5位
  • 3、以每一位為中心的迴文串的長度也要記錄,後面進行推斷的時候能用到,就像案例中用到的以第3位為中心的迴文和第4位為中心的迴文
  • 4、對於新的中心,我們判斷它是否在右邊界內,若在,就計算它相對右邊界迴文中心的對稱位置,從而得到一些資訊,同時,如果該中心需要進行擴充套件,則繼續擴充套件就行。

程式設計實現

小史: 迴文的中心有可能是兩個字元中間,這種情況沒有考慮到啊。

小史:

  • 1、先對字串進行預處理,兩個字元之間加上特殊符號#;
  • 2、然後遍歷整個字串,用一個數組來記錄以該字元為中心的迴文長度,為了方便計算右邊界,我在陣列中記錄長度的一半(向下取整);
  • 3、每一次遍歷的時候,如果該字元在已知迴文串最右邊界的覆蓋下,那麼就計算其相對最右邊界迴文串中心對稱的位置,得出已知迴文串的長度;
  • 4、判斷該長度和右邊界,如果達到了右邊界,那麼需要進行中心擴充套件探索。當然,如果第3步該字元沒有在最右邊界的“羽翼”下,則直接進行中心擴充套件探索。進行中心擴充套件探索的時候,同時又更新右邊界;
  • 5、最後得到最長迴文之後,去掉其中的特殊符號即可。

理解了演算法之後,小史的程式碼寫起來也是非常快,不一會兒就寫好了: PlalindromeString.java:

/**
 * @author xiaoshi on 2018/9/24.
 * Happy Mid-Autumn Festival
 */
public class PlalindromeString {

    // 判斷一個字串是否迴文,演算法中用不到了
    @Deprecated
    private boolean isPlalindrome(String s) {
        int len = s.length();
        for(int i = 0; i < len / 2; i++) {
            if(s.charAt(i) != s.charAt(len - 1 - i)) {
                return false;
            }
        }
        return true;
    }

    // 預處理字串,在兩個字元之間加上#
    private String preHandleString(String s) {
        StringBuffer sb = new StringBuffer();
        int len = s.length();
        sb.append('#');
        for(int i = 0; i < len; i++) {
            sb.append(s.charAt(i));
            sb.append('#');
        }
        return sb.toString();
    }

    // 尋找最長迴文字串
    public String findLongestPlalindromeString(String s) {
        // 先預處理字串
        String str = preHandleString(s);
        // 處理後的字串長度
        int len = str.length();
        // 右邊界
        int rightSide = 0;
        // 右邊界對應的迴文串中心
        int rightSideCenter = 0;
        // 儲存以每個字元為中心的迴文長度一半(向下取整)
        int[] halfLenArr = new int[len];
        // 記錄迴文中心
        int center = 0;
        // 記錄最長迴文長度
        int longestHalf = 0;
        for(int i = 0; i < len; i++) {
            // 是否需要中心擴充套件
            boolean needCalc = true;
            // 如果在右邊界的覆蓋之內
            if(rightSide > i) {
                // 計算相對rightSideCenter的對稱位置
                int leftCenter = 2 * rightSideCenter - i;
                // 根據迴文性質得到的結論
                halfLenArr[i] = halfLenArr[leftCenter];
                // 如果超過了右邊界,進行調整
                if(i + halfLenArr[i] > rightSide) {
                    halfLenArr[i] = rightSide - i;
                }
                // 如果根據已知條件計算得出的最長迴文小於右邊界,則不需要擴充套件了
                if(i + halfLenArr[leftCenter] < rightSide) {
                    // 直接推出結論
                    needCalc = false;
                }
            }
            // 中心擴充套件
            if(needCalc) {
                while(i - 1 - halfLenArr[i] >= 0 && i + 1 + halfLenArr[i] < len) {
                    if(str.charAt(i + 1 + halfLenArr[i]) == str.charAt(i - 1 - halfLenArr[i])) {
                        halfLenArr[i]++;
                    } else {
                        break;
                    }
                }
                // 更新右邊界及中心
                rightSide = i + halfLenArr[i];
                rightSideCenter = i;
                // 記錄最長迴文串
                if(halfLenArr[i] > longestHalf) {
                    center = i;
                    longestHalf = halfLenArr[i];
                }
            }
        }
        // 去掉之前新增的#
        StringBuffer sb = new StringBuffer();
        for(int i = center - longestHalf + 1; i <= center + longestHalf; i += 2) {
            sb.append(str.charAt(i));
        }
        return sb.toString();
    }

}

Main.java:

/**
 * @author lixin on 2018/9/24.
 */
public class Main {

    public static void main(String[] args) {

        PlalindromeString ps = new PlalindromeString();

        String[] testStrArr = new String[] {
            "abcdcef",
            "adaelele",
            "cabadabae",
            "aaaabcdefgfedcbaa",
            "aaba",
            "aaaaaaaaa"
        };

        for(String str : testStrArr) {
            System.out.println(String.format("原字串 : %s", str));
            System.out.println(String.format("最長迴文串 : %s", ps.findLongestPlalindromeString(str)));
            System.out.println();
        }

    }

}
執行結果:

原字串 : abcdcef
最長迴文串 : cdc

原字串 : adaelele
最長迴文串 : elele

原字串 : cabadabae
最長迴文串 : abadaba

原字串 : aaaabcdefgfedcbaa
最長迴文串 : aabcdefgfedcbaa

原字串 : aaba
最長迴文串 : aba

原字串 : aaaaaaaaa
最長迴文串 : aaaaaaaaa

時間空間分析