leetcode-5-最長迴文子串
目錄
- Posted by 微博@Yangsc_o
- 原創文章,版權宣告:自由轉載-非商用-非衍生-保持署名 | Creative Commons BY-NC-ND 3.0
本題是leetcode,地址:5. 最長迴文子串
題目
給定一個字串 s,找到 s 中最長的迴文子串。你可以假設 s 的最大長度為 1000。
示例 1:
輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
示例 2:輸入: "cbbd"
輸出: "bb"
分析
題目還是很好理解的,思路有大致三種,一種暴力解、一種動態規劃、一種是中心擴散法;
暴力解決分析:根據迴文子串的定義,列舉所有長度大於等於 2的子串,依次判斷它們是否是迴文,在列舉的過程中需要記錄“當前子串的起始位置”和“子串長度”,最後通過substring擷取一下對應的字串就好了。很容發現,這種解法效率很低!時間複雜度:O(N^3),N是字串的長度,列舉字串的左邊界、右邊界,然後繼續驗證子串是否是迴文子串,這三種操作都與 NN 相關;
code-暴力解決
public boolean check(String s, int left, int right) { while (left < right) { if (s.charAt(left) != s.charAt(right)) { return false; } left++; right--; } return true; } public String longestPalindrome(String s) { if (s == null || s.length() < 1) { return ""; } int start = 0, maxLenght = 1; char[] chars = s.toCharArray(); for (int left = 0; left < chars.length; left++) { for (int right = chars.length - 1; right > left; right--) { boolean check = check(s, left, right); if (check && right - left + 1 > maxLenght) { start = left; maxLenght = right - left + 1; break; } } } return s.substring(start, start + maxLenght); }
code-動態規劃
在設計動態規劃時,需要考慮的點如下:
思考狀態
思考狀態轉移方程
思考初始化
思考輸出
思考優化空間
迴文字串的特點:
如果一個字串的頭尾兩個字元都不相等,那麼這個字串一定不是迴文串;
如果一個字串的頭尾兩個字元相等,才有必要繼續判斷下去。
如果裡面的子串是迴文,整體就是迴文串;
如果裡面的子串不是迴文串,整體就不是迴文串。
如果dp[i] [j] 表示子串 s[i..j]
是否為迴文子串,這裡子串 s[i..j]
定義為左閉右閉區間,可以取到 s[i]
和 s[j]
- 狀態轉換公式:
dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
邊界條件:表示式 [i + 1, j - 1]
不構成區間,即長度嚴格小於 2
,即 j - 1 - (i + 1) + 1 < 2
,即 j - i < 3
。怎麼理解?即s[i..j]
的長度等於 2
或者等於 3
的時候,其實只需要判斷一下頭尾兩個字元是否相等就可以直接下結論,而不需要參考之前的結果;
- 初始化:單個字元一定是迴文串,因此把對角線先初始化為
true
,即dp[i][i] = true
- 輸出:
dp[i][j] = true
,記錄子串的長度和起始位置,當然我們是找最長的,需要跟上次記錄的做比較;
時間複雜度:O(N^2)
根據以上資訊就可以寫我們的程式碼了
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int begin = 0, maxLen = 1;
// 狀態記錄
boolean[][] dp = new boolean[len][len];
// 初始化:單個字元一定是迴文串,因此把對角線先初始化為 `true`,即 `dp[i][i] = true`
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
for (int j = 1; j < len ; j ++) {
for (int i = 0; i < len; i ++) {
// 判斷是否相等
if (s.charAt(i) != s.charAt(j)) {
dp[i][j] = false;
} else {
// 邊界條件:即`s[i..j]` 的長度等於 `2` 或者等於 `3`
if (j - i < 3) {
dp[i][j] = true;
} else {
// 狀態轉換公式:dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1]
dp[i][j] = dp[i + 1][j - 1];
}
}
// 記錄begin和長度
if (dp[i][j] && j - i + 1 > maxLen) {
begin = i;
maxLen = j - i + 1;
}
}
}
return s.substring(begin, begin + maxLen);
}
code-中心擴散法
暴力法採用雙指標兩邊夾,驗證是否是迴文子串。如果列舉可能出現的迴文子串的“中心位置”,從“中心位置”嘗試儘可能擴散出去,得到一個迴文串。遍歷每一個索引,以這個索引為中心,利用“迴文串”中心對稱的特點,往兩邊擴散,看最多能擴散多遠。列舉“中心位置”時間複雜度為 O(N),從“中心位置”擴散得到“迴文子串”的時間複雜度為 O(N),時間複雜度可以降到 O(N^2)。
- 奇數迴文串的“中心”是一個具體的字元,例如:迴文串 "aba" 的中心是字元 "b";
- 偶數迴文串的“中心”是位於中間的兩個字元的“空隙”,例如:迴文串串 "abba" 的中心是兩個 "b" 中間的那個“空隙”。
public String longestPalindrome3(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int start = 0, maxLength = 1;
for (int i = 1; i < len; i++) {
int one = around(s, i, i);
int two = around(s, i, i + 1);
int length = Math.max(one, two);
if (length > maxLength) {
start = i - (length - 1) / 2;
maxLength = length;
}
}
return s.substring(start, start + maxLength);
}
public int around(String s, int left, int right) {
while (left > 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
left--;
right++;
}
return right - left - 1;
}