目录
最长回文子串-LeetCode(Java)
题目:5. 最长回文子串
给你一个字符串 s
,找到 s
中最长的回文子串。
如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。
示例 1:
输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。
示例 2:
输入:s = "cbbd" 输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
分析:
一.暴力解法(超时)
1.思路:
截取每一段子串,然后判断它是不是回文串,如果是的话,就更新最长回文子串。
2.代码:
class Solution {
// 辅助函数,用于检查字符串 s 中从索引 begin 到 end 的子串是否为回文
boolean isPalindrome(String s, int begin, int end) {
while (begin <= end) {
// 如果两端的字符不同,则不是回文
if (s.charAt(begin) != s.charAt(end))
return false;
// 向中间移动
begin++;
end--;
}
// 如果所有字符都相同,则是回文
return true;
}
// 主函数,用于找出字符串 s 中的最长回文子串
public String longestPalindrome(String s) {
int ansl = 0, ansr = 0; // 存储最长回文子串的起始和结束索引
// 外层循环遍历子串的起始位置
for (int i = 0; i < s.length(); i++)
// 内层循环遍历子串的结束位置
for (int j = i; j < s.length(); j++)
// 检查子串是否是回文
if (isPalindrome(s, i, j)) {
// 如果找到更长的回文子串,则更新最长回文子串的索引
if (j - i > ansr - ansl) {
ansr = j;
ansl = i;
}
}
// 返回最长回文子串
return s.substring(ansl, ansr + 1);
}
}
时间复杂度:O(n^3)
二.中心扩展算法
1.思路
从每个可能的中心开始,尽可能向两边扩展,当两个方向的字母不再相同时,我们就找到了以该中心为起点的最长回文串。
2.代码
class Solution {
public String longestPalindrome(String s) {
if(s == null || s.length()<1)
return "";
int start = 0;//最长回文子串的起始位置
int 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){//更新最长回文子串的位置
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() && s.charAt(left) == s.charAt(right)){
left--;//左拓展
right++;//右拓展
}
return right - left - 1;//返回拓展后的回文长度
}
}
时间复杂度:O(n^2)
三.动态规划
来自官方题解
1.思路
2.代码
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
int maxLen = 1;
int begin = 0;
// dp[i][j] 表示 s[i..j] 是否是回文串
boolean[][] dp = new boolean[len][len];
// 初始化:所有长度为 1 的子串都是回文串
for (int i = 0; i < len; i++) {
dp[i][i] = true;
}
char[] charArray = s.toCharArray();
// 递推开始
// 先枚举子串长度
for (int L = 2; L <= len; L++) {
// 枚举左边界,左边界的上限设置可以宽松一些
for (int i = 0; i < len; i++) {
// 由 L 和 i 可以确定右边界,即 j - i + 1 = L 得
int j = L + i - 1;
// 如果右边界越界,就可以退出当前循环
if (j >= len) {
break;
}
if (charArray[i] != charArray[j]) {
dp[i][j] = false;
} else {
if (j - i < 3) {
dp[i][j] = true;
} else {
dp[i][j] = dp[i + 1][j - 1];
}
}
// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此时记录回文长度和起始位置
if (dp[i][j] && j - i + 1 > maxLen) {
maxLen = j - i + 1;
begin = i;
}
}
}
return s.substring(begin, begin + maxLen);
}
}
复杂度分析
时间复杂度:O(n^2),其中 n 是字符串的长度。动态规划的状态总数为O(n^2),对于每个状态,我们需要转移的时间为O(1)。
空间复杂度:O(n^2),即存储动态规划状态需要的空间。
四.Manacher算法
1.思路
马拉车算法 Manacher‘s Algorithm 是用来查找一个字符串的最长回文子串的线性方法,由一个叫 Manacher 的人在1975年发明,这个方法的最大贡献是在于将时间复杂度提升到了线性。
Manacher算法会先在每个字符之间插入一个未在字符串中出现过的字符(如#),再利用回文半径和回文直径,依次匹配,再通过判断i(当前位置)与R(回文半径)的关系进行不同的分支操作,接着继续遍历直到遍历完整个字符串。
参阅吴师兄这篇内容:老司机开车,教会女朋友什么是「马拉车算法」_吴师兄学算法
2.代码
public class Solution {
public String longestPalindrome(String s) {
int len = s.length();
if (len < 2) {
return s;
}
String str = addBoundaries(s, '#');
int sLen = 2 * len + 1;
int maxLen = 1;
int start = 0;
for (int i = 0; i < sLen; i++) {
int curLen = centerSpread(str, i);
if (curLen > maxLen) {
maxLen = curLen;
start = (i - maxLen) / 2;
}
}
return s.substring(start, start + maxLen);
}
private int centerSpread(String s, int center) {
// left = right 的时候,此时回文中心是一个空隙,回文串的长度是奇数
// right = left + 1 的时候,此时回文中心是任意一个字符,回文串的长度是偶数
int len = s.length();
int i = center - 1;
int j = center + 1;
int step = 0;
while (i >= 0 && j < len && s.charAt(i) == s.charAt(j)) {
i--;
j++;
step++;
}
return step;
}
/**
* 创建预处理字符串
*
* @param s 原始字符串
* @param divide 分隔字符
* @return 使用分隔字符处理以后得到的字符串
*/
private String addBoundaries(String s, char divide) {
int len = s.length();
if (len == 0) {
return "";
}
if (s.indexOf(divide) != -1) {
throw new IllegalArgumentException("参数错误,您传递的分割字符,在输入字符串中存在!");
}
StringBuilder stringBuilder = new StringBuilder();
for (int i = 0; i < len; i++) {
stringBuilder.append(divide);
stringBuilder.append(s.charAt(i));
}
stringBuilder.append(divide);
return stringBuilder.toString();
}
}
- 时间复杂度:O(N2),这里 N 是原始字符串的长度。新字符串的长度是 2 * N + 1,不计系数与常数项,因此时间复杂度仍为 O(N2)。
- 空间复杂度:O(N)。