[LeetCode] 5. 最長迴文子串
阿新 • • 發佈:2020-08-17
目錄
給定一個字串
s
,找到 s
中最長的迴文子串。你可以假設 s
的最大長度為 1000。
輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
輸入: "cbbd"
輸出: "bb"
分析
- 列舉所有子串,判斷是否是迴文
- 中心拓展演算法,有2n-1箇中心,可穿插符號簡化
- 求最長公共子串,使用動態規劃,注意檢查子串索引是否與反向子串原始索引相同
- 如果i,j為迴文,並且S(i-1)等於S(j+1),則i-1,j+1也是迴文子串,使用動態規劃
- Manacher演算法時間複雜度為O(n)
解法
解法一
-
列舉所有子串
-
時間複雜度O(\(n^3\))
-
空間複雜度O(1)
class Solution { public String longestPalindrome(String s) { String ans = ""; int max = 0, len = s.length(); // 遍歷起點 for(int i = 0; i < len; i++){ // 遍歷終點,排除剩餘長度小於max的情況 for(int j = i + 1; j < len + 1 && len - i > max; j++){ // 擷取子串 String test = s.substring(i, j); // 判斷是否為迴文,且長度大於max if(isPalindrome(test) && test.length() > max){ ans = test; max = test.length(); } } } return ans; } // 判斷是否為迴文 private boolean isPalindrome(String s){ int len = s.length(); for(int i = 0; i < len/2; i++){ if(s.charAt(i) != s.charAt(len - i -1)) return false; } return true; } } //class Test{ // public static void main(String[] args) { // Solution s = new Solution(); // String res = s.longestPalindrome("babad"); // System.out.println(res); // } //}
解法二
-
中心擴充套件演算法
-
穿插#字元,迴文子串中心一側的字元數即為該子串長度,如#a|#|a#或#a#|b|#a#
-
時間複雜度O(\(n^2\))
-
空間複雜度O(1)
class Solution { public String longestPalindrome(String s) { // 穿插#字元 String str = "#"; for(int i = 0; i < s.length(); i++){ str += s.charAt(i) + "#"; } int center = 0, maxLen = 0; // 遍歷擴充套件中心 for(int i = 0; i < str.length(); i++){ int len = expandAroundCenter(str, i); if(len > maxLen){ center = i; maxLen = len; } } return s.substring((center - maxLen) / 2, (center + maxLen + 1) / 2); } private int expandAroundCenter(String str, int i){ int r = 0; while(i -r >= 0 && i + r <= str.length() - 1 && str.charAt(i - r) == str.charAt(i + r)){ r++; } // r多加了一次 return r - 1; } }
-
不穿插#字元
-
時間複雜度O(\(n^2\))
-
空間複雜度O(1)
class Solution {
public String longestPalindrome(String s) {
if(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 + 1){
start = i - (len - 1) / 2;
end = i + len / 2;
}
}
return s.substring(start, end + 1);
}
private int expandAroundCenter(String s, int left, int right){
while(left >= 0 && right <= s.length() - 1 &&
s.charAt(left) == s.charAt(right)){
left--;
right++;
}
return right - left - 1;// (right - 1) - (left + 1) + 1, 回退到上一位置計算
}
}
解法三
- 動態規劃求最長公共子串
-
二維陣列,若字元相等,arr[i][j]=arr[i-1][j-1]+1
i=0或j=0時,字元相等為1
可使用一維陣列減小空間複雜度,j需從下向上更新,因為需要根據上面的元素計算下面的元素
注意類似abc123cba情況,子串索引和反向子串原始索引不同
-
時間複雜度O(\(n^2\))
-
空間複雜度O(n)(一維陣列)
class Solution {
public String longestPalindrome(String s) {
if(s.equals("")) return "";
// 原始字串
String origin = s;
// 反轉字串
String reverse = new StringBuffer(s).reverse().toString();
int length = s.length();
int[] arr = new int[length];
int maxLen = 0;
int maxStart = 0;
for(int i = 0; i < length; i++){
// j從下向上更新
for(int j = length - 1; j >= 0; j--){
if(origin.charAt(i) == reverse.charAt(j)){
// i為0或j為0時若字元相等元素為1
if(i == 0 || j == 0){
arr[j] = 1;
}else{
// 若字元相等元素為上一元素加1
arr[j] = arr[j-1] + 1;
}
}else{
// 若字元不相等元素為0
arr[j] = 0;
}
if(arr[j] > maxLen){
int beforeRev = length - 1 -j;
// 排除位置不對應情況
if(beforeRev + arr[j] - 1== i){
maxLen = arr[j];
maxStart = beforeRev;
}
}
}
}
return s.substring(maxStart, maxStart + maxLen);
}
}
解法四
- 動態規劃
-
根據(i+1,j-1)確定(i,j)是否為迴文子串,不再呼叫判斷函式,空間換時間
狀態轉移方程:dp[i][j] = s[i]==s[j] && dp[i+1][j-1]
dp[i+1][j-1]為左下單元格,使用一維陣列需從下至上,從右至左遍歷i和j((5,5)->(4,5)-(4,4)->(3,5)->(3,4)->(3,3)...)
i+1>j-1時,即j-i<2,長度為1、2時需單獨判斷(可結合圖看出)
-
時間複雜度O(\(n^2\))
-
空間複雜度O(n)(一維陣列)
class Solution {
public String longestPalindrome(String s) {
if(s.equals("")) return "";
int length = s.length();
boolean[] dp = new boolean[length];
// 最長迴文子串長度及起始索引
int maxLen = 1;
int maxStart = 0;
int maxEnd = 0;
// 從下至上,從右至左遍歷i和j
for(int i = length - 1; i >= 0; i--){
for(int j = length - 1; j >= i; j--){
// 長度為1、2時單獨判斷
dp[j] = s.charAt(i) == s.charAt(j) && (j - i < 2 || dp[j - 1]);
// 若長度大於maxLen,更新子串起始索引
if(dp[j] && j - i + 1 > maxLen){
maxLen = j - i + 1;
maxStart = i;
maxEnd = j;
}
}
}
return s.substring(maxStart, maxEnd + 1);
}
}