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


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;
    }
}

小結

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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM