LeetCode第五題:
給定一個字符串
s
,找到s
中最長的回文子串。你可以假設s
的最大長度為 1000。
示例 1:
輸入: "babad"
輸出: "bab"
注意: "aba" 也是一個有效答案。
示例 2:
輸入: "cbbd"
輸出: "bb"
這道題做了是真的久。其實想想並不難。一開始的時候是算法完全錯了,進入了思維的誤區。一直在debug,不斷測試,然后都是部分測試可以通過,部分就是不通過。以為自己只是沒有考慮到一些邊界問題,然后每改一次,就多通過一些,但是任然有的會錯誤。到第二天依然不行。中文睡午覺得時候,沒睡着,一直在腦中復現,突然就發現是我寫的算法本身的問題。 然后就開始想別的算法。想到了一個遞歸算法,這回沒錯,但是當字符串很長的時候,就會棧溢出,不能滿足題目需要。然后就把遞歸算法改成了普通循環,這回又變成了超時。真的是在做這題的時候什么都遇到了。分析了一下時間復雜的,時間復雜度是
n^3
。最后是看了分析,最后寫了本文最后的算法,時間復雜的n^2
.
第一次嘗試
一開始沒有仔細想,就開始做了。於是就想錯了。算法如下:
算法一:
class Solution {
public String longestPalindrome(String s) {
//子串長度
int slength = 0;
//長串移位
int move = 0;
//長串長度
int l = s.length();
//字符數組個數
int m = 0;
//選定的數組序號
int n = 0;
//記錄長度
int t = 0;
char[][] stringChar = new char[1000][l];
char[] chars = s.toCharArray();
int j = l - 1;
while (slength <= l - move) {
//組內循環號
int k = 0;
boolean flag = false;
for (int i = 0; i < l && j >= 0; i++) {
if (chars[i] == chars[j]) {
stringChar[m][k] = chars[i];
flag = true;
k++;
j--;
continue;
}
if (flag && k > t) {
int temp = n;
n = m;
t = k ;
m=temp;
// m++;
k = 0;
j = l-move-1;
flag = false;
}
if (flag) {
j = l-move-1;
// m++;
flag = false;
}
}
if (flag && k > t) {
n = m;
t = k ;
}
slength = t;
move++;
j = l-move-1;
if (j<=0) break;
}
StringBuffer sb = new StringBuffer();
for (int i = 0;i<t;i++){
sb.append(stringChar[n][i]);
}
return sb.toString();
}
}
主要的思想是把該字符串逆轉,想想有兩個指針,一開始分別置於正字符串和反字符串的頭上。
第一次
cbada
adabc
第二次
cbada
adabc
第三次
cbada
adabc
如果不匹配,則正字符串的指針移位,反字符串不動。如匹配,則正反同時移位,直到不匹配,將匹配成功的子字符串與一開始的比較,取長者。
總之這個過程就是不斷的移位,匹配的過程。可以看到,代碼中也是寫了很多的期指變量,看着難受,寫着也頭疼。花費了大量的時間解決邊界問題,但是最重要的是,該問題不能被這樣解決!如下:
tabgat
tagbst
可以看tabgat到,字串
tabgat
的最大回文字串就是單個字符。但是使用算法一,就會被判斷為最長回文子串為ta
,所以該算法根本不可行。
第二次嘗試
還是躺着比較容易思考.
否決了之前的算法后,一切都舒暢多了。突然想到,尋找最長子串可以用遞歸的思路來想。
當一個問題可以分解為類型相同,但規模不同的子問題的時候,且有最終狀態,就可以用遞歸解決。
- 尋找最字符串
s
的最長回文串,找到則返回。 - 尋找比
s
的長度短1的字符串的最長回文串 - 當字符串為空或長度為一時,則直接返回。
這里我也遇到了一個問題,那就是在第二步尋找子問題的時候。因為比原串s
短1的子串有兩個,去頭或去尾。然后一開始寫出了錯誤的遞歸算法:
class Solution {
public String longestPalindrome(String s) {
if (isPalinedrome(s)) return s;
String s1 = s.substring(0,s.length()-1);
String s2 = s.substring(1,s.length());
return findNext(s1,s2);
}
//判斷是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一個子串
String findNext(String s1, String s2) {
if (isPalinedrome(s1)) return s1;
if (isPalinedrome(s2)) return s2;
return findNext(s1.substring(0,s1.length()-1),s2.substring(1,s2.length()));
}
}
看起來好像情況都考慮了,實則沒有。在第20行這里。會一直執行,直到返回回文,或到最后返回單個字符。s2永遠不會被考慮。
后來我就想到了如下的算法:
class Solution {
public String longestPalindrome(String s) {
return findNext(s, s.length(), 0);
}
//判斷是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一個子串 s母串,n子串長度,index子串序號
String findNext(String s, int n, int index) {
if (isPalinedrome(s.substring(index, index + n))) return s.substring(index, index + n);
else {
if (index < s.length() - n) index++;
else {
n--;
index = 0;
}
}
return findNext(s, n, index);
}
}
依次尋找下一個字符串。可行。然后使用
for
循環,改成了非遞歸的方式:
class Solution {
public String longestPalindrome(String s) {
return findNext(s, s.length(), 0);
}
//判斷是否回文
boolean isPalinedrome(String s) {
if ("".equals(s)) return true;
char[] chars = s.toCharArray();
for (int i = 0; i < s.length(); i++) {
if (i >= s.length() - i -1) return true;
if (chars[i] != chars[s.length() - i - 1]) return false;
}
return false;
}
//找到下一個子串 s母串,n子串長度,index子串序號
String findNext(String s, int n, int index) {
while (!isPalinedrome(s.substring(index, index + n))) {
if (index < s.length() - n) index++;
else {
n--;
index = 0;
}
}
return s.substring(index, index + n);
}
}
依次驗證每一個子串是不是回文。子串共有(1+n)*n/2個,每個子串驗證回文時間復雜度為n,所以時間復雜的為n^3。在LeetCode驗證超時。
今天寫了最終版。分別以每個字符或倆字符間隙為中心,從外擴散。找到以之為中心的最長回文,與原來的比較,取長者,最終返回。時間復雜度為n^2.
class Solution {
public String longestPalindrome(String s) {
String palindrome = "";
char[] chars = s.toCharArray();
for (int i = 0; i < (2 * s.length())-1; i++) {
int big = 0;
while (i + big < ((2 * s.length())-1) && i - big >= 0 && (chars[(i - big)/2] == chars[(i + big)/2])) {
if (i % 2 != 0) {
if (big == 0) {big++;}
else if (chars[(i - big)/2] == chars[(i + big)/2]) {
big = big + 2;
}
}
else {
if (chars[(i - big)/2] == chars[(i + big)/2]) {
big = big + 2;
}
}
}
big = big - 2;
if (big >= palindrome.length()) {
palindrome = s.substring((i - big) / 2, (i + big) / 2+1);
}
}
return palindrome;
}
}
小結
這道題我真的遇到了好幾種問題,也寫了好久。最大的感受就是其實只要好好想清楚了,寫起來就是一會的事。一旦陷入思維的誤區,還不走出來,就會萬劫不復。動手前,真的要好好想清楚思路,不要太着急。