1. 程式人生 > >LeetCode刷題筆記 5

LeetCode刷題筆記 5

題目:給定一個字串 s,找到 s 中最長的迴文子串。假設 s 的最大長度為 1000。
eg:輸入: “babad” 輸出: “bab” 注意: “aba” 也是一個有效答案。
輸入: “cbbd” 輸出: “bb”

迴文串就是一個正讀和反讀都一樣的字串

答案:
方法一:最長公共子串。
反轉 S,使之變成 S’。找到 S和 S’之間最長的公共子串,這也必然是最長的迴文子串。
但是這個方法有bug 需要糾正。S=“abacdfgdcaba” , S’ =“abacdgfdcaba”:S 以及 S’之間的最長公共子串為 “abacd”,顯然,這不是迴文。我們可以看到,當 SS 的其他部分中存在非迴文子串的反向副本時,最長公共子串法就會失敗。為了糾正這一點,每當我們找到最長的公共子串的候選項時,都需要檢查子串的索引是否與反向子串的原始索引相同。如果相同,那麼我們嘗試更新目前為止找到的最長迴文子串;如果不是,我們就跳過這個候選項並繼續尋找下一個候選。
時間複雜度O(n2

)
空間複雜度O(n2)

方法二:暴力法
遍歷所有子字串,判斷是否是迴文,並返回最大長度。

class Solution{
    public String longestPalindrome(String s) {
        int n = s.length();
        int i,j,start=0,end=0,max=-1,flag=0;
        if(s == null || n == 0) return "";
        for(i=0;i<n;i++)
        {
            for(j=i;j<n;j++)
            {
                start = i;
                end = j;
                while(start <= end)
                {
                    if(s.charAt(start) == s.charAt(end))
                    {
                        start++;
                        end--;
                    }
                    else break;
                }
                //首尾相遇則為迴文
                if(start > end)
                {
                    if(j-i > max)
                    {
                        max = j-i;
                        flag = i;
                    }
                }
            }
        }
        return s.substring(flag,flag+max+1);
    }
}

時間複雜度O(n3)
空間複雜度O(1)

假設輸入:abcddcba,按照上述程式,要分割成 'abcddcba’, 'bcddcb’, 'cddc’, 'dd’…等字串,並對這些字串分別進行判斷。不難發現,很多短子字串在長些的子字串中比較過,這導致了大量的冗餘判斷,根本原因是:對字串對稱的判斷是由外向裡進行的。
換一種思路,從裡向外來判斷。也就是先判斷子字串(如dd)是不是對稱的。如果它(dd)不是對稱的,那麼向該子字串兩端各延長一個字元得到的字串肯定不是對稱的。如果它(dd)對稱,那麼只需要判斷它(dd)兩端延長的一個字元是不是相等的,如果相等,則延長後的字串是對稱的。

方法三:中心擴充套件演算法

class Solution{
    public String longestPalindrome(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);
            int len2 = expandAroundCenter(s, i, i + 1);
            int len = Math.max(len1, len2);
            if (len > end - start) {
                start = i - (len - 1) / 2;
                end = i + len / 2;
            }
        }
        return s.substring(start, end + 1);
    }

    private int expandAroundCenter(String s, int left, int right) {
        int L = left, R = right;
        while (L >= 0 && R < s.length() && s.charAt(L) == s.charAt(R)) {
            L--;
            R++;
        }
        return R - L - 1;
    }
}

時間複雜度O(n2)
空間複雜度O(1)

方法四:Manacher演算法
演算法的基本思路是這樣的:把原串每個字元中間用一個串中沒出現過的字元分隔#開來(統一奇偶),同時為了防止越界,在字串的首部也加入一個特殊符$,但是與分隔符不同。同時字串的末尾也加入’\0’。演算法的核心:用輔助陣列p記錄以每個字元為核心的最長迴文字串半徑。也就是p[i]記錄了以str[i]為中心的最長迴文字串半徑。p[i]最小為1,此時迴文字串就是字串本身。

//預處理,將str:abba轉換為: $#a#b#b#a#\0(從1開始)
char * pre(char *str)
{
    int length = strlen(str);
    char *prestr = new char[2*length + 4];
    prestr[1] = '$';
    for(int i=0;i<length;i++)
    {
        prestr[2*(i+1)] = '#';
        prestr[2*(i+1)+1] = str[i];
    }
    prestr[2*length+2]='#';
    prestr[2*length+3]='\0';
    return prestr;
}
//manacher演算法。pi記錄具有遍歷過程中最長半徑的迴文字串中心字串。mx記錄了具有最長迴文字串的右邊界。
int getMaxSym3(char *str)
{
    char *prestr = pre(str);
    int mx =0, pi=1;//邊界和對稱中心
    int len = strlen(prestr);
    //輔助陣列
    int *p = new int[len];
    p[0] = 0;
    for(int i=1;i<len;i++)
    {
        if(mx>i)
        {
            p[i]=min(mx-i,p[2*pi-i]);//核心
        }
        else
        {
            p[i]=1;
        }
        while(prestr[i-p[i]]==prestr[i+p[i]]&&i-p[i]>0&&i+p[i]<len)
        {
            p[i]++;
        }
        if(i+p[i] > mx)
        {
            mx = p[i] + i;
            pi = i;
        }
    }
    //最大回文字串長度
    int maxlen = 0;
    for(int i=0;i<len;i++)
    {
        if(p[i]>maxlen)
        {
            maxlen = p[i];
        }
    }
    delete []prestr;
    delete []p;
    return maxlen - 1;
}

需要注意的問題:

  1. 理清思路,最好將不同情況分隔開,不要混在一起
  2. char 單引號 string 雙引號。 字串不能直接比較相等,字元可以
  3. 有些邊界問題可以加一個特殊字元輔助

參考連結:https://www.cnblogs.com/heyonggang/p/3386724.html