【算法】最长回文子串
文章目录
- 题目
- 方法一:中心扩展法
-
- 解题思路
- 代码实现
- 复杂度
- 方法二:动态规划
-
- 解题思路
- 代码实现
- 复杂度
- 方法三:Manacher算法
-
- 解题思路
- 代码实现
- 复杂度
- 总结
题目
示例 1
:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。
示例 2
:
输入:s = “cbbd”
输出:“bb”
方法一:中心扩展法
解题思路
- 遍历字符串s中的每个字符,以每个字符为中心向两侧扩展,找到以当前字符为中心的最长回文子串。
- 同时,考虑回文串长度为偶数的情况,以当前字符和下一个字符为中心向两侧扩展,找到以这两个字符为中心的最长回文子串。
- 在扩展过程中,记录最长回文子串的起始和结束位置。
- 最终返回最长回文子串,可以通过substring方法截取子串。
代码实现
public class Solution {public String longestPalindrome(String s) {if (s == null || s.length() == 0) {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);}// 中心扩展法,返回以left和right为中心的回文子串的长度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),其中n为字符串s的长度
时间复杂度
:O(1),因为只使用了常数个变量
方法二:动态规划
解题思路
- 使用动态规划来解决问题,定义一个二维布尔型数组 dp,其中
dp[i][j]
表示字符串 s 中从索引 i 到 j 的子串是否为回文串。 - 初始化 dp 数组,当 i 和 j 相等时,表示单个字符是回文串,即
dp[i][i] = true
;当s.charAt(i) == s.charAt(j)
且j - i <= 2
时,表示长度为 2 的子串是回文串,即dp[i][j] = true
。 - 遍历 dp 数组,从长度为 3 的子串开始,依次更新 dp 数组。对于 dp[i][j],如果
s.charAt(i) == s.charAt(j)
且dp[i+1][j-1]
为 true,则表示从索引 i 到 j 的子串是回文串,即dp[i][j] = true
。 - 在遍历过程中记录最长的回文子串,返回最长回文子串。
代码实现
public String longestPalindrome(String s) {int len = s.length();boolean[][] dp = new boolean[len][len];String longestPalindrome = "";int maxLength = 0;// 初始化长度为1的回文子串for (int i = 0; i < len; i++) {dp[i][i] = true;maxLength = 1;longestPalindrome = s.substring(i, i + 1);}// 初始化长度为2的回文子串for (int i = 0; i < len - 1; i++) {if (s.charAt(i) == s.charAt(i + 1)) {dp[i][i + 1] = true;maxLength = 2;longestPalindrome = s.substring(i, i + 2);}}// 动态规划遍历和更新for (int l = 3; l <= len; l++) {for (int i = 0; i <= len - l; i++) {int j = i + l - 1;if (s.charAt(i) == s.charAt(j) && dp[i + 1][j - 1]) {dp[i][j] = true;if (l > maxLength) {maxLength = l;longestPalindrome = s.substring(i, j + 1);}}}}return longestPalindrome;
}
复杂度
时间复杂度
:O(n^2),其中 n 是字符串 s 的长度,因为要遍历二维数组 dp
时间复杂度
:O(n^2),需要使用二维数组 dp 来记录状态信息
方法三:Manacher算法
Manacher 算法是一种高效的解决回文子串问题的算法,其时间复杂度为 O(n)
,其中 n 是字符串的长度。以下是 Manacher 算法的解题思路、代码实现以及复杂度分析
解题思路
- Manacher 算法的核心思想是利用回文串的对称性,避免重复的比较,从而实现线性时间复杂度。
- 首先,将原始字符串进行预处理,将每个字符之间插入一个特殊的字符(如 “#”),以处理偶数长度的回文串。
- 定义一个辅助数组 P,用来记录以每个字符为中心的回文串的半径长度(包括中心字符)。
- 初始化辅助数组 P,将每个元素初始化为 1,因为每个字符本身都是一个回文串。
- 从左到右遍历字符串,利用中心扩展法找到以每个字符为中心的回文串的半径长度,并更新辅助数组 P。
- 在遍历过程中记录当前找到的最长回文串,并记录其结束位置和长度,以便最后返回结果。
代码实现
public class Solution {public String longestPalindrome(String s) {// 预处理字符串,在字符之间插入特殊字符 "#"String t = preprocess(s);// Manacher 算法核心部分int n = t.length();int[] dp = new int[n]; // 动态规划数组,记录每个字符作为回文中心时的回文半径长度int center = 0; // 当前回文串的中心int right = 0; // 当前回文串的右边界for (int i = 0; i < n; i++) {// 如果当前字符 i 在当前回文串的右边界内,可以利用回文对称性直接计算 dp[i]if (i < right) {int j = 2 * center - i; // i 关于 center 的对称点 jdp[i] = Math.min(right - i, dp[j]);}// 尝试将当前字符 i 向两侧扩展,更新 dp[i]int left = i - (dp[i] + 1); // 当前回文串的左边界int rightBound = i + (dp[i] + 1); // 当前回文串的右边界while (left >= 0 && rightBound < n && t.charAt(left) == t.charAt(rightBound)) {dp[i]++;left--;rightBound++;}// 更新当前回文串的中心和右边界if (i + dp[i] > right) {center = i;right = i + dp[i];}}// 找出最长回文子串int maxLen = 0; // 最长回文子串的长度int centerIndex = 0; // 最长回文子串的中心索引for (int i = 0; i < n; i++) {if (dp[i] > maxLen) {maxLen = dp[i];centerIndex = i;}}// 提取最长回文子串StringBuilder result = new StringBuilder();for (int i = centerIndex - maxLen; i <= centerIndex + maxLen; i++) {if (t.charAt(i) != '#') {result.append(t.charAt(i));}}// 去除预处理时插入的特殊字符 "#"return result.toString();}// 预处理字符串,在字符之间插入特殊字符 "#"private String preprocess(String s) {StringBuilder sb = new StringBuilder();for (char c : s.toCharArray()) {sb.append("#");sb.append(c);}sb.append("#");return sb.toString();}
}
复杂度
时间复杂度
: O(n),其中 n 是输入字符串的长度
空间复杂度
: O(n),因为需要使用额外的动态规划数组来保存每个字符
总结
-
中心扩展法:遍历字符串的每个字符,将其作为回文串的中心,向两侧扩展,判断扩展的字符是否相同,记录扩展的长度,最后找到最长回文子串。时间复杂度为O(n^2),空间复杂度为O(1)。
-
动态规划法:利用回文串的动态规划性质,通过动态规划数组记录每个字符作为回文中心时的回文半径长度,避免重复计算,从而找到最长回文子串。时间复杂度为O(n^2),空间复杂度为O(n)。
-
Manacher算法:利用回文串的对称性,通过动态规划的方式避免重复计算,从而在线性时间内找到最长回文子串。时间复杂度为O(n),空间复杂度为O(n)。
综上所述,中心扩展法
是简单但效率较低的方法,动态规划法
通过优化中心扩展法减少了重复计算,而Manacher算法
则是最优的方法,具有线性时间复杂度。在实际应用中,可以根据具体情况选择合适的方法来解决寻找最长回文子串的问题