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个小时还不会,改天一定补上/补上啦