Copyright © Zi10ng
道阻且長
- 題目位址
題目描述
給定一個字元串 s,找到 s 中最長的回文子串。你可以假設 s 的最大長度為 1000。
示例 1:
輸入: “babad”
輸出: “bab”
題目思路
- 剛看到這個題的時候,想到了兩種做法,一種是O(3),另外一種是O(2)
- 第一種是從左到右,從右到左,從子串的兩邊開始周遊每一個子字元串并核對比較,即完全暴力
- 第二種是從左到右,從子串的中間到兩邊擴充,即中心擴充法
動态規劃
之後發現了動态規劃算法也可解
給定一個字元串
abcba
dp的過程是:
- 1.先看ab是否是回文串,得
dp[0][1]==0
- 2.再看abc是否為回文串,得
,其中需要根據dp[0][2]==0
d[1][1]==1
- 3.再看abcd是否為回文串,得
,其中需要根據dp[0][3]==0
,dp[1][2]==0
- 3.1我們需要注意,在此之前,以經計算出了
dp[1][3]==1
- 4.再看abcd是否為回文串,得
,其中需要根據dp[0][4]==1
,dp[1][3]==1
馬拉車
最後一種是針對于回文子串的馬拉車算法:
給定一個字元串
abcba
- 先給字元串進行加工:
,保證了個數為奇數#a#b#c#b#a
- 我們需要知道,設半徑為
,則以r[index]
為中心的回文子串的長度為index
r[index]-1
- 如果我們已知以
為中心的字元串是回文串,那麼我們可以通過性質得知其之後到index為2的回文串結束的回文子串的長度index=2
- 連結1
- 連結2
解法
我的算法①
/**
* 我的算法,三重循環。好像是叫滑動視窗來着?
* 第一重從前往後,第二重從後往前,第三重用來判斷
* 增加一個loop和if中的left<=right是為了防止多餘循環
* @param s 給定字元串
* @return 字元串
*/
public String longestPalindrome(String s) {
String longestSubString = "";
int length = s.length();
for(int left = 0; left < length; left++){
LOOP:
for (int right = length - 1; right >= 0; right --) {
if (s.charAt(left) == s.charAt(right) && left <= right) {
for (int tempLeft = left, tempRight = right; s.charAt(tempLeft) == s.charAt(tempRight); tempLeft ++, tempRight --){
if (tempLeft == tempRight || tempLeft + 1 == tempRight){
longestSubString = s.substring(left,right + 1).length() > longestSubString.length() ? s.substring(left,right + 1):longestSubString;
break LOOP;
}
}
}
}
}
return longestSubString;
}
我的算法②
/**
* 中心擴充法,雙重循環
* @param s 給定字元串
* @return 傳回字元串
*/
public String centerExpansion(String s){
String longestSubString = "";
String centerLongestSubString = "";
int length = s.length();
for (int i = 0; i < length; i++) {
centerLongestSubString = centerExpansionSubString(s,i,i + 1).length() > centerExpansionSubString(s,i,i).length() ? centerExpansionSubString(s,i,i + 1) : centerExpansionSubString(s,i,i);
longestSubString = longestSubString.length() > centerLongestSubString.length() ? longestSubString : centerLongestSubString;
}
return longestSubString;
}
/**
* centerExpansion子算法,但是通過這個逾時了
* 目的是考慮了奇偶性
* @param s 傳入字元串
* @param left 左邊index
* @param right 右邊index
* @return 字元串
*/
private String myCenterExpansionSubString(String s, int left, int right){
String longestSubString = "";
for (int centerLeft = left, centerRight = right; centerLeft >=0 && centerRight < s.length(); centerLeft--, centerRight++){
if (s.charAt(centerLeft) == s.charAt(centerRight)){
longestSubString = s.substring(centerLeft,centerRight + 1).length() > longestSubString.length() ? s.substring(centerLeft,centerRight + 1) : longestSubString;
}else {
break;
}
}
return longestSubString;
}
/**
* 更新了myCenterExpansionSubString算法
* @param s 傳入字元串
* @param left 左邊index
* @param right 右邊index
* @return 字元串
*/
private String centerExpansionSubString(String s, int left, int right){
while (left >=0 && right < s.length() && s.charAt(left) == s.charAt(right)){
left--;
right++;
}
return s.substring(left + 1,right );
}
動态規劃
/**
* 動态規劃
* 一般最優求解都是動态規劃找出狀态轉移方程(二維數組的上三角形式),降低記憶體可通過滾動數組
* @param s 給定字元串
* @return 傳回最長子串
*/
public String dP(String s){
String longestSubString = "";
int length = s.length();
boolean[][] dp = new boolean[length][length];
for (int right = 0; right < length; right++) {
for (int left = 0; left <= right; left ++){
if (s.charAt(right) == s.charAt(left) && (right - left <= 2 || dp[left + 1][right - 1])){
dp[left][right] = true;
longestSubString = right - left + 1 > longestSubString.length() ? s.substring(left, right + 1) : longestSubString;
}
}
}
return longestSubString;
}
馬拉車
/**
* 馬拉車算法,可以結合KMP一塊看,專用于解決回文子串,o(1)
* @param s 給定字元串
* @return 回文子串
*/
public String manacher(String s) {
String longestSubString = "";
//對s進行加工使其變成奇數個
StringBuilder stringBuilder = new StringBuilder("#");
for (int i = 0; i < s.length(); i++) {
stringBuilder.append(s.charAt(i)).append("#");
}
int length = stringBuilder.length();
//定義半徑
int[] r = new int[length];
//定義mid和mid+r
int mid = 0, max = 0;
for (int i = 0; i < length; i++) {
if (i < max){
r[i] = Integer.min(max - i, r[mid * 2 - i]);
}else {
r[i] = 1;
}
//上邊的if是馬拉車的精髓,即通過abadabaxx中的d的半徑計算出倒數第二個b的半徑,但是它隻能計算出b的半徑是2
//下面的while循環就是計算出b的半徑可能會大于2,如abadabada,此時b的半徑是4
while (i - r[i] >= 0 && i + r[i] < length && stringBuilder.charAt(i + r[i]) == stringBuilder.charAt(i - r[i])){
r[i] ++;
}
//接下來該更新max和mid的值了
if (max < i + r[i]){
max = i +r[i];
mid = i;
}
if (longestSubString.length() < r[i] - 1){
longestSubString = stringBuilder.substring(i - r[i] + 1, i + r[i]).replace("#", "");
System.out.println(longestSubString);
}
}
return longestSubString;
}
感悟
- 一般求最優解都可用到DP,二維數組的上三角,如果優化記憶體可用到滾動數組
- 馬拉車有點難了解,看了1個小時還不會,改天一定補上/補上啦