1. 程式人生 > >LeetCode第五題:尋找最長回文子串

LeetCode第五題:尋找最長回文子串

mpi flow Language 類型 gpg nrv 需要 cccccc cgo

LeetCode第五題:

給定一個字符串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。

示例 1:

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

示例 2:

輸入: "cbbd"
輸出: "bb"

這道題做了是真的久。其實想想並不難。一開始的時候是算法完全錯了,進入了思維的誤區。一直在debug,不斷測試,然後都是部分測試可以通過,部分就是不通過。以為自己只是沒有考慮到一些邊界問題,然後每改一次,就多通過一些,但是任然有的會錯誤。到第二天依然不行。中文睡午覺得時候,沒睡著,一直在腦中復現,突然就發現是我寫的算法本身的問題。 然後就開始想別的算法。想到了一個遞歸算法,這回沒錯,但是當字符串很長的時候,就會棧溢出,不能滿足題目需要。然後就把遞歸算法改成了普通循環,這回又變成了超時。真的是在做這題的時候什麽都遇到了。分析了一下時間復雜的,時間復雜度是 n^3

。最後是看了分析,最後寫了本文最後的算法,時間復雜的n^2.

第一次嘗試

一開始沒有仔細想,就開始做了。於是就想錯了。算法如下:

算法一:
class Solution {
    public String longestPalindrome(String s) {
                //子串長度
        int slength = 0;
        //長串移位
        int move = 0;
        //長串長度
        int l = s.length();
        //字符數組個數
        int m = 0;
        //選定的數組序號
int n = 0; //記錄長度 int t = 0; char[][] stringChar = new char[1000][l]; char[] chars = s.toCharArray(); int j = l - 1; while (slength <= l - move) { //組內循環號 int k = 0; boolean flag = false; for (int i = 0; i < l && j >= 0
; i++) { if (chars[i] == chars[j]) { stringChar[m][k] = chars[i]; flag = true; k++; j--; continue; } if (flag && k > t) { int temp = n; n = m; t = k ; m=temp; // m++; k = 0; j = l-move-1; flag = false; } if (flag) { j = l-move-1; // m++; flag = false; } } if (flag && k > t) { n = m; t = k ; } slength = t; move++; j = l-move-1; if (j<=0) break; } StringBuffer sb = new StringBuffer(); for (int i = 0;i<t;i++){ sb.append(stringChar[n][i]); } return sb.toString(); } }

主要的思想是把該字符串逆轉,想想有兩個指針,一開始分別置於正字符串和反字符串的頭上。

第一次
cbada 
adabc

第二次
cbada 
 adabc

第三次
cbada 
  adabc

如果不匹配,則正字符串的指針移位,反字符串不動。如匹配,則正反同時移位,直到不匹配,將匹配成功的子字符串與一開始的比較,取長者。

總之這個過程就是不斷的移位,匹配的過程。可以看到,代碼中也是寫了很多的期指變量,看著難受,寫著也頭疼。花費了大量的時間解決邊界問題,但是最重要的是,該問題不能被這樣解決!如下:

tabgat
tagbst

可以看tabgat到,字串tabgat 的最大回文字串就是單個字符。但是使用算法一,就會被判斷為最長回文子串為ta ,所以該算法根本不可行。

第二次嘗試

還是躺著比較容易思考.

否決了之前的算法後,一切都舒暢多了。突然想到,尋找最長子串可以用遞歸的思路來想。

當一個問題可以分解為類型相同,但規模不同的子問題的時候,且有最終狀態,就可以用遞歸解決。

  1. 尋找最字符串s 的最長回文串,找到則返回。
  2. 尋找比s 的長度短1的字符串的最長回文串
  3. 當字符串為空或長度為一時,則直接返回。

這裏我也遇到了一個問題,那就是在第二步尋找子問題的時候。因為比原串s 短1的子串有兩個,去頭或去尾。然後一開始寫出了錯誤的遞歸算法:

class Solution {
        public String longestPalindrome(String s) {
        if (isPalinedrome(s)) return s;
        String s1 = s.substring(0,s.length()-1);
        String s2 = s.substring(1,s.length());
        return findNext(s1,s2);
    }
    //判斷是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一個子串
    String findNext(String s1, String s2) {
        if (isPalinedrome(s1)) return s1;
        if (isPalinedrome(s2)) return s2;
        return findNext(s1.substring(0,s1.length()-1),s2.substring(1,s2.length()));
    }
}

看起來好像情況都考慮了,實則沒有。在第20行這裏。會一直執行,直到返回回文,或到最後返回單個字符。s2永遠不會被考慮。

後來我就想到了如下的算法:

class Solution {
     public String longestPalindrome(String s) {
        return findNext(s, s.length(), 0);
    }
    //判斷是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一個子串 s母串,n子串長度,index子串序號
    String findNext(String s, int n, int index) {
        if (isPalinedrome(s.substring(index, index + n))) return s.substring(index, index + n);
        else {
            if (index < s.length() - n) index++;
            else {
                n--;
                index = 0;
            }
        }
        return findNext(s, n, index);
    }
}

依次尋找下一個字符串。可行。然後使用for 循環,改成了非遞歸的方式:

class Solution {
     public String longestPalindrome(String s) {
        return findNext(s, s.length(), 0);
    }
    //判斷是否回文
    boolean isPalinedrome(String s) {
        if ("".equals(s)) return true;
        char[] chars = s.toCharArray();
        for (int i = 0; i < s.length(); i++) {
            if (i >= s.length() - i -1) return true;
            if (chars[i] != chars[s.length() - i - 1]) return false;
        }
        return false;
    }
    //找到下一個子串 s母串,n子串長度,index子串序號
    String findNext(String s, int n, int index) {
        while (!isPalinedrome(s.substring(index, index + n))) {
            if (index < s.length() - n) index++;
            else {
                n--;
                index = 0;
            }
        }
        return s.substring(index, index + n);
    }
}

依次驗證每一個子串是不是回文。子串共有(1+n)*n/2個,每個子串驗證回文時間復雜度為n,所以時間復雜的為n^3。在LeetCode驗證超時。

今天寫了最終版。分別以每個字符或倆字符間隙為中心,從外擴散。找到以之為中心的最長回文,與原來的比較,取長者,最終返回。時間復雜度為n^2.

class Solution {
        public String longestPalindrome(String s) {
        String palindrome = "";
        char[] chars = s.toCharArray();
        for (int i = 0; i < (2 * s.length())-1; i++) {
            int big = 0;
            while (i + big < ((2 * s.length())-1) && i - big >= 0 && (chars[(i - big)/2] == chars[(i + big)/2])) {
                if (i % 2 != 0) {
                    if (big == 0) {big++;}
                    else if (chars[(i - big)/2] == chars[(i + big)/2]) {
                        big = big + 2;
                    }
                }
                else {
                    if (chars[(i - big)/2] == chars[(i + big)/2]) {
                        big = big + 2;
                    }
                }
            }
            big = big - 2;
            if (big >= palindrome.length()) {
                palindrome = s.substring((i - big) / 2, (i + big) / 2+1);
            }
        }
        return palindrome;
    }
}

小結

這道題我真的遇到了好幾種問題,也寫了好久。最大的感受就是其實只要好好想清楚了,寫起來就是一會的事。一旦陷入思維的誤區,還不走出來,就會萬劫不復。動手前,真的要好好想清楚思路,不要太著急。

?

LeetCode第五題:尋找最長回文子串