最长回文子串
难度
中等
题目
给你一个字符串 s
,找到 s
中最长的 回文 子串。
示例 1:
输入:s = "babad"
输出:"bab"
解释:"aba"
同样是符合题意的答案。
示例 2:
输入:s = "cbbd"
输出:"bb"
提示:
1 <= s.length <= 1000
s
仅由数字和英文字母组成
暴力法
对于这道题,最简单也是最容易想到的的解法莫过于暴力法,直接遍历出所有的子串,再去递归判断每一个子串是不是回文子串,最后再比较找出的所有回文子串,找出最长的那一个返回。
优点
1.简单直观:暴力法通过检查所有可能的子串来判断是否是回文子串,思路非常直接。
2.递归实现:代码实现使用递归函数检测回文,逻辑清晰。
缺点
1.效率低:时间复杂度为 O(n^3),对于每个子串都要重新计算回文状态,计算量巨大,尤其在字符串长度较大时,性能问题突出。
2.冗余计算:许多子串的回文状态会被重复计算,导致大量不必要的计算。
代码(暴力法)
// 判断字符串 s 在 [left, right] 区间是否为回文串
int palindrome(int left, int right, char *s) {
if (left >= right) // 递归停止条件:left >= right 时表示子串已经验证完毕,是回文
{
return 1;
}
if (s[left] == s[right]) // 如果当前左右字符相同,则继续递归检查内层字符
{
return palindrome(left + 1, right - 1, s); // 递归调用,检查内层字符
} else {
return 0; // 如果左右字符不相同,则子串不是回文
}
}
// 找到字符串 s 中的最长回文子串
char* longestPalindrome(char* s) {
int length = strlen(s); // 获取字符串长度
int max = 0; // 记录最长回文子串的长度
int start = 0, finish = 0; // 用于记录最长回文子串的开始和结束位置
// 遍历字符串的所有子串
for (int i = 0; i < length; i++) {
for (int j = length - 1; j >= 0; j--) {
if (palindrome(i, j, s) == 1) // 检查当前子串是否为回文子串
{
if ((j - i + 1) > max) // 如果当前回文子串的长度大于记录的最大长度
{
max = j - i + 1; // 更新最大长度
start = i; // 更新最长回文子串的起始位置
finish = j; // 更新最长回文子串的结束位置
}
}
}
}
// 返回最长回文子串
if (max == 0) // 如果没有找到回文子串,返回 NULL
{
return NULL;
}
char* result = (char*)malloc((max + 1) * sizeof(char)); // 分配内存以存储最长回文子串
strncpy(result, s + start, max); // 将最长回文子串复制到新分配的内存中
result[max] = '\0'; // 添加字符串结束符
return result; // 返回最长回文子串
}
中心扩展法
中心扩展法其基本思想是从字符串的每一个字符或者两个字符之间的空隙作为中心,向两侧扩展,以找到回文串的边界。由于回文串具有对称性质,因此以这种方式扩展能够有效减少重复计算,提高算法的效率。
优点
1.效率高:时间复杂度为 O(n^2),相比暴力法大大减少了计算量。
2.无冗余计算:每个中心只会扩展一次,不会重复计算相同的子串。
3.空间复杂度低:只需要常数级别的额外空间。
缺点
实现复杂度略高:相比暴力法,中心扩展法的实现需要理解回文的对称性和边界处理,逻辑较为复杂。
代码(中心扩展法)
// 在给定的索引 'left' 和 'right' 处找到最长回文的长度
int expandAroundCenter(char* s, int length, int left, int right) {
// 从中心向外扩展,同时检查两边字符是否相等且在范围内
while (left >= 0 && right < length && s[left] == s[right]) {
left--; // 左指针向左移动
right++; // 右指针向右移动
}
// 当 while 循环退出时,left 和 right 超出了回文的边界
// 因此,回文的长度是 (right - left - 1)
return right - left - 1; // 减去多移动的一步,得到正确的长度
}
char* longestPalindrome(char* s) {
int length = strlen(s); // 获取字符串的长度
if (length == 0) { // 如果字符串长度为0,则直接返回空字符串
return "";
}
int start = 0, finish = 0; // 用于记录最长回文子串的起始和结束位置
// 以字符串的每个字符作为中心进行遍历
for (int i = 0; i < length; i++) {
int len1 = expandAroundCenter(s, length, i, i); // 奇数长度的回文子串
int len2 = expandAroundCenter(s, length, i, i + 1); // 偶数长度的回文子串
int len = (len1 > len2) ? len1 : len2; // 选择以该字符为中心的最长回文子串
// 如果找到的回文子串比之前记录的最长回文子串长
if (len > finish - start) {
start = i - (len - 1) / 2; // 更新最长子串起始位置
finish = i + len / 2; // 更新最长子串结束位置
}
}
int max = finish - start + 1; // 计算最长回文子串的长度
char* result = (char*)malloc((max + 1) * sizeof(char)); // 分配内存以存储最长回文子串
strncpy(result, s + start, max); // 把最长回文子串放到新内存中
result[max] = '\0'; // 字符串结束符
return result;
}
总结
1.暴力法:适用于理解回文子串概念的初学者,代码简单,但效率低,时间复杂度为O(n^3),不适合处理长字符串。
2.中心扩展法:更高效,适用于实际应用,时间复杂度为 O(n^2),是解决最长回文子串问题的常用方法。
如果有疑问可以在评论区提问,希望这篇博客能帮助你更好地理解和应用这两种算法!