LeetCode 5.最長的迴文字串
給定一個字串s,找出其中最長的迴文格式的子字串。你可以假設長度的最大值為1000.
Example:
Input: "babad"
Output: "bab"
複製程式碼
Note: "aba" is also a valid answer.
Example:
Input: "cbbd"
Output: "bb"
複製程式碼
一開始以為palindrome是重複的意思,走了很大的彎路,後來才知道指的是迴文格式,就是一個順著讀和反過來讀都一樣的字串。 由此可知他有兩種情況,一種是奇數的情況,中間的一個字元獨立,其餘的字元以中間為軸兩兩對應。另一種是偶數的情況,所有的字元都以中間為軸兩兩對應。
方法一:
public class Solution {
private int lo, maxLen;
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2)
return s;
for (int i = 0; i < len - 1; i++) {
extendPalindrome(s, i, i); //assume odd length, try to extend Palindrome as possible
extendPalindrome(s, i, i + 1); //assume even length.
}
return s.substring(lo, lo + maxLen);
}
private void extendPalindrome(String s, int j, int k) {
while (j >= 0 && k < s.length() && s.charAt(j) == s.charAt(k)) {
j--;
k++;
}
if (maxLen < k - j - 1) {
lo = j + 1;
maxLen = k - j - 1;
}
}
}
複製程式碼
分析 這個方法分為奇偶兩種情況進行計算。首先對每一個字串裡的字元進行兩種情況的計算,extendPalindrome方法的原理就是當對應的位置的字元相同時,就將左側字元向左一位,右側字元向右一位,然後再重複進行這個比較過程。 時間複雜度 : O(n^2) 。n是字串的長度 空間複雜度 : O(n) .
方法二:
public class Solution {
public String longestPalindrome(String s) {
String res = "";
int currLength = 0;
for(int i=0;i<s.length();i++){
if(isPalindrome(s,i-currLength-1,i)){
res = s.substring(i-currLength-1,i+1);
currLength = currLength+2;
}
else if(isPalindrome(s,i-currLength,i)){
res = s.substring(i-currLength,i+1);
currLength = currLength+1;
}
}
return res;
}
public boolean isPalindrome(String s, int begin, int end){
if(begin<0) return false;
while(begin<end){
if(s.charAt(begin++)!=s.charAt(end--)) return false;
}
return true;
}
}
複製程式碼
分析 這個方法主要是每當指標向右移時,我們都以這個位置的字元為結尾看是否能有新的長度為length(current length +1 或者 current length +2)的迴文字串。
時間複雜度 : O(n^2) 。n是字串長度。 空間複雜度 : O(n) .
方法三 Manacher演算法
public class Solution {
public String longestPalindrome(String s) {
char[] str = changeString(s);
String result = manacher(str,s);
return result;
}
/**
* 返回例如 #a#c#b#c#a#a#c#b#c#d#形式的字串陣列
*
* @param s
* @return
*/
public static char[] changeString(String s)
{
char[] str = new char[s.length() * 2 + 1];
int i = 0;
for (; i < s.length(); i++)
{
str[2 * i] = '#';
str[2 * i + 1] = s.charAt(i);
}
str[2 * i] = '#';
return str;
}
/**
* manacher 演算法實現找到迴文字串最長的一個
*/
public static String manacher(char[] s,String olds)
{
String result = "";
int rad[] = new int[s.length];
int start = 0;
int end = 0;
//i index,j 迴文半徑,k
int i = 1, j = 0, k;
// 記錄最長的迴文串的長度
int maxLen = 0;
while (i < s.length)
{
// 掃描得出rad值
while (i - j - 1 > -1 && i + j + 1 < s.length
&& s[i - j - 1] == s[i + j + 1]) {
j++;
}
if (maxLen < j ) {
maxLen =j;
start = i - j ;
end =i + j ;
}
rad[i] = j;
maxLen = maxLen > j ? maxLen : j;
k = 1;
//當迴文中包含子迴文,看子迴文是否超出父迴文邊界。 分三種情況。
while (k <= rad[i] && rad[i - k] != rad[i] - k)
{
rad[i + k] = Math.min(rad[i - k], rad[i] - k);
k++;
}
i = i + k;
j = Math.max(j - k, 0);
}
result = olds.substring(start/2,end/2);
return result;
}
}
複製程式碼
分析 在這個問題中,迴文的情況一共有兩種,一種是奇數迴文,一種是偶數迴文,為了將他們合併成一種情況,我們可以在首尾和每兩個字元中間加上一個特殊字元,如‘#’,形如"#a#b#b#c#a#".這樣我們就將所有的迴文情況合併成奇數迴文的情況。
在字串s中,我們用rad[i]表示第i個字元的迴文半徑,可以得到s[i-rad[i],i-1] = s[i+1,i+rad[i]],只要求出了所有的rad,就求出了所有奇數長度的迴文子串。
當我們得到了rad[1..i-1]的值,並通過比較對稱字元得到當前字元i的rad值至少為j,求出了rad[i]。現在我們設一個指標k,從1迴圈到rad[i],以此來求出[i+1,i+rad[i]]的rad值。
根據定義,黑色的部分是一個迴文子串,兩段紅色的區間全等。 因為之前已經求出了rad[i-k],所以直接用它.有3種情況: (1)rad[i]-k < rad[i-k]
如圖,rad[i-k]的範圍為墨綠色。因為黑色的部分是迴文的,且墨綠色的部分超過了黑色的部分,所以rad[i+k]至少為rad[i]-k,即橙色的部分。有沒有可能rad[i+k]的值大於rad[i]-k呢?這是不可能發生的,根據迴文的特性我們知道,如果橙色部分以外也是迴文,那麼黑色的迴文部分就可以向外擴充。與假設衝突。因此rad[i+k] = rad[i]-k。
(2)rad[i]-k > rad[i-k]
如圖,rad[i-k]的範圍為墨綠色。因為黑色的部分是迴文的,且墨綠色的部分在黑色的部分裡面,根據迴文特性,很容易得出:rad[i+k] = rad[i-k]。
根據上面兩種情況,可以得出結論:當rad[i]-k != rad[i-k]的時候,rad[i+k] = min(rad[i]-k,rad[i-k])。
(3)rad[i]-k = rad[i-k]
如圖,通過和第一種情況對比之後會發現,因為墨綠色的部分沒有超出黑色的部分,所以即使橙色的部分全等,也無法像第一種情況一樣引出矛盾,因此橙色的部分是有可能全等的。但是,根據已知的資訊,我們不知道橙色的部分是多長,因此就把i指標移到i+k的位置,j=rad[i-k](因為它的rad值至少為rad[i-k]),是否可以向外擴充等下個迴圈再做。
時間複雜度 : O(n) 。看起來程式裡用到了迴圈巢狀,但是實際上他只對沒有計量過的i進行計量。 空間複雜度 : O(n) .
如果你有更好的辦法或者對我這裡的描述有其他看法,請聯絡我。謝謝 原文地址
About Me
我的部落格 leonchen1024.com
我的 GitHub github.com/LeonChen102…
微信公眾號