LeetCode 5 (Longest Palindromic Substring)

Longest Palindromic Substring(最大回文字符串)

1、題目描述:

Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

Example 1:

Input: "babad"
Output: "bab"
Note: "aba" is also a valid answer.
Example 2:

Input: "cbbd"
Output: "bb"

給出一個(gè)字符串s,找出長度最大的回文子串,s的最大長度小于1000。

2、摘要:

下面介紹了幾種方法實(shí)現(xiàn):回文,動(dòng)態(tài)規(guī)劃和字符串操作。回文的定義:一個(gè)字符串從兩個(gè)方向讀,它的內(nèi)容是相同的。例如:S = "aba"是回文字符串,而S = "abc"不是回文字符串。

3、解決方法:

方法1:Brute Force(暴力破解)

很明顯,暴力破解就是找到所有子串驗(yàn)證它是否是回文字符串。

Java實(shí)現(xiàn):

 public boolean isPalindrome(String s) {
        String ss = new StringBuilder(s).reverse().toString();
        if (ss.equals(s)) {
            return true;
        }
        return false;
    }

 public String longestPalindrome(String s) {
        int longestLength = 0;
        String longestSubString = "";

        for (int i = 0; i < s.length(); i++) {
            for (int j = i+1; j <= s.length(); j++) {
                String subString = s.substring(i,j);

                if (isPalindrome(subString) && subString.length()>longestLength){
                    longestLength = subString.length();
                    longestSubString = subString;
                }
            }
        }

        return longestSubString;
    }

時(shí)間復(fù)雜度:
??兩個(gè)for循環(huán)中嵌套了一個(gè)判斷回文的過程,回文判斷我使用的是StringBuilder的reverse()方法,時(shí)間復(fù)雜度一共是O(n^3)。比較不理想,提交上去會(huì)出現(xiàn)時(shí)間超時(shí)。

空間復(fù)雜度:兩個(gè)變量,復(fù)雜度為O(1)。

方法2:Longest Common Substring(最長的公共子串)

??一些人可能會(huì)想出一個(gè)最快的方法,倒序字符串s,然后與原字符串對(duì)比,然后找出最長的公共子串,這個(gè)子串一定就是最長的回文子串。
??從表面上看這個(gè)方法是正確的,但是仔細(xì)想來并不是完全正確,例如S = "abacdfgdcaba",他和倒序的公共最長字符為 "abacd",然而這個(gè)并不是回文字符串。導(dǎo)致出現(xiàn)這個(gè)情況的原因是原字符串中存在一個(gè)非回文倒序副本。如果要排除這個(gè)影響,就要在候選字符串中 檢查子串的索引是否與反向子串的原始索引相同,相同就保留,不同就舍棄。

??首先實(shí)現(xiàn)尋找最長的公共子串,具體步驟參考:
https://blog.csdn.net/u010397369/article/details/38979077
??具體實(shí)現(xiàn)思路就是把兩個(gè)字符串組成一個(gè)二維數(shù)組 ,如果兩個(gè)對(duì)應(yīng)字符相等,就執(zhí)行 temp[ i ][ j ] = temp[ i - 1 ][ j - 1] + 1。因?yàn)閕-1或者j-1會(huì)越界,所以可以單獨(dú)處理。temp[ i ][ j ] 保存的就是公共子串的長度。

Java實(shí)現(xiàn)

public String longestPalindrome_2(String s) {

       if (s.equals("")) {
            return "";
        }

        String ss = new StringBuilder(s).reverse().toString();  //倒序
        int longestlength = 0;
        int maxEnd = 0;
        int[][] temp = new int[s.length()][ss.length()];
        char[] s_char = s.toCharArray();
        char[] ss_char = ss.toCharArray();
        //原字符串做列,倒序后的子串作為行
        for (int i = 0; i < ss_char.length; i++) {
            for (int j = 0; j < s_char.length; j++) {
                if (s_char[i] == ss_char[j]) {
                    if (i == 0 || j == 0) {
                        temp[i][j] = 1;
                    } else {
                        temp[i][j] = temp[i - 1][j - 1] + 1;
                    }
                }
                if (temp[i][j] > longestlength) {
                    longestlength = temp[i][j];
                    maxEnd = i;
                }
            }
        }
        return s.substring(maxEnd - longestlength + 1, maxEnd + 1);
    }

??以上算法只能實(shí)現(xiàn)尋找最長的公共子串,如果s="abc435cba",公共子串為"abc",但是這個(gè)不是回文字符串。為了解決這個(gè)問題,我們還要對(duì)比子串在倒序后的字符串的位置和原字符串的位置是否對(duì)應(yīng)。
??舉個(gè)例子,如果s="caba",s' = "abac",他們的最長回文串為"aba","aba"在原字符串中的位置為 1 2 3 ,在s'中的位置為 0 1 2,所以 aba 就是我們需要找的。當(dāng)然我們不需要每個(gè)字符都判斷,我們只需要判斷末尾字符就可以。
如圖:

image.png

??i所指的字符a在原字符串中的位置為beforeRev = length - i- 1 = 0beforeRev就是在j中為第一個(gè)字符位置,且 beforeRev + temp[i][j] - 1 =2代表j中最后一個(gè)字符的位置,如果位置與j相等,aba就是要找的。我們可以寫出如下代碼:

   //動(dòng)態(tài)規(guī)劃 (獲取最長回文串) 需要和原字符對(duì)比位置
    public String longestPalindrome_3(String s) {
        if (s.length() <= 1) {
            return s;
        }
        String ss = new StringBuilder(s).reverse().toString();  //倒序
        int longestlength = 0;
        int maxEnd = 0;
        int[][] temp = new int[s.length()][ss.length()];
        char[] s_char = s.toCharArray();
        char[] ss_char = ss.toCharArray();
        //原字符串做列,倒序后的子串作為行
        for (int i = 0; i < ss_char.length; i++) {
            for (int j = 0; j < s_char.length; j++) {
                if (s_char[i] == ss_char[j]) {
                    if (i == 0 || j == 0) {
                        temp[i][j] = 1;
                    } else {
                        temp[i][j] = temp[i - 1][j - 1] + 1;
                    }
                }
                if (temp[i][j] > longestlength) {

                    /*******************增加的部分***********************/
                    int beforeRev = s.length() - i - 1;
                    if (beforeRev + temp[i][j] - 1 == j) {
                        longestlength = temp[i][j];
                        maxEnd = i;
                    }
                }
            }
        }
        return s.substring(maxEnd - longestlength + 1, maxEnd + 1);
    }

執(zhí)行時(shí)間:


image.png

時(shí)間復(fù)雜度:兩個(gè)嵌套循環(huán),O(n^2)
空間復(fù)雜度:一個(gè)二維數(shù)組,O(n^2)

??仔細(xì)觀察可以發(fā)現(xiàn),我們判斷字符相等的只用到了temp[i][j],一行用過之后就棄置不用了。所以我們可以把空間復(fù)雜度優(yōu)化到O(n),只需要把一個(gè)一維數(shù)組重新賦值即可,因?yàn)檎蛸x值有可能覆蓋改后面需要使用的數(shù)據(jù)
比如a[3] = a[2]+1時(shí),計(jì)算a[4]的時(shí)候a[3]的值就不是原來的了。所以我們需要從后往前計(jì)算,代碼如下:

    public String longestPalindrome_4(String s) {

        if (s.equals("")) {
            return "";
        }

        String ss = new StringBuilder(s).reverse().toString();  //倒序

        int longestlength = 0;
        int maxEnd = 0;
        int[] temp = new int[s.length()];

        char[] s_char = s.toCharArray();
        char[] ss_char = ss.toCharArray();


        for (int i = 0; i < s_char.length; i++) {    //初始化第一行
            temp[i] = (s_char[0] == ss_char[i]) ? 1 : 0;
        }

        for (int i = 0; i < s_char.length; i++) {
            for (int j = ss.length() - 1; j >= 0; j--) {

                if (s_char[i] == ss_char[j]) {

                    if (i == 0 || j == 0) {
                        temp[j] = 1;
                    } else {
                        temp[j] = temp[j - 1] + 1;
                    }

                    if (temp[j] > longestlength) {

                        /*******************增加的部分***********************/
                        int beforeRev = s.length() - j - 1;
                        if (beforeRev + temp[j] - 1 == i) {
                            longestlength = temp[j];
                            maxEnd = i;
                        }
                    }
                } else {
                    temp[j] = 0;
                }
            }
        }
        return s.substring(maxEnd - longestlength + 1, maxEnd + 1);
    }

運(yùn)行時(shí)間:


image.png

時(shí)間復(fù)雜度:兩個(gè)嵌套循環(huán),O(n^2)
空間復(fù)雜度:一個(gè)一維數(shù)組,O(n)

方法3:擴(kuò)展中心

??我們觀察到一個(gè)回文串是從一個(gè)中心到兩邊的鏡像。所以,回文串可以從一個(gè)字符(奇數(shù))或兩個(gè)字符(偶數(shù))的為中心拓展,它的中心總共有2n-1個(gè)。以i為中心左邊為left,右邊為right,先令left=right=i,滿足left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)時(shí),left--;right++;向外拓展,直到結(jié)束。回文的長度就是right - left - 1。實(shí)現(xiàn)如下:
Java實(shí)現(xiàn):

    //方法 擴(kuò)展中心
    public int expandAroundCenter(String s, int left, int right) {
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
        }
        return right - left - 1;
    }

    public String longestPalindrome_6(String s) {
        if (s == null || s.length() < 1)
            return "";

        int start = 0, end = 0;
        for (int i = 0; i < s.length(); i++) {
            int len1 = expandAroundCenter(s, i, i); //奇數(shù)
            int len2 = expandAroundCenter(s, i, i + 1);//偶數(shù)
            int len = Math.max(len1, len2);
            if (len > end - start) { 
                  //重新計(jì)算start 和end
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);
    }

運(yùn)行時(shí)間如下:


image.png

??因?yàn)橹蛔R(shí)別回文序列,過濾掉了很大部分情況,雖然時(shí)間復(fù)雜度為o(n^2),但是執(zhí)行效率更高。
時(shí)間復(fù)雜度:兩個(gè)嵌套循環(huán),最壞的情況下,O(n^2)
空間復(fù)雜度:O(1)

參考:
https://leetcode.com/problems/longest-palindromic-substring/solution/
http://windliang.cc/2018/08/05/leetCode-5-Longest-Palindromic-Substring/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容