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;
}
需要注意的問題:
- 理清思路,最好將不同情況分隔開,不要混在一起
- char 單引號 string 雙引號。 字串不能直接比較相等,字元可以
- 有些邊界問題可以加一個特殊字元輔助