当前位置: 首页 > news >正文

【力扣100题】53.最长回文子串

题目描述

给你一个字符串 s,找到 s 中最长的回文子串。

示例

示例 1: 输入:s = "babad" 输出:"bab" 解释:"aba" 同样是符合题意的答案。 示例 2: 输入:s = "cbbd" 输出:"bb" 约束:1 <= s.length <= 1000

解题思路总览

方法核心思想时间复杂度空间复杂度备注
暴力枚举枚举所有子串,判断是否回文O(n^3)O(1)会超时
动态规划dp[i][j] = s[i]==s[j] && dp[i+1][j-1]O(n^2)O(n^2)空间较大
中心扩展从中心向两边扩展O(n^2)O(1)最常用
Manacher马拉车算法,O(n)O(n)O(n)最优但复杂

一、中心扩展法(推荐)

核心思想

回文串是中心对称的。选择一个中心,向两边同时扩展,遇到不匹配的字符就停止。这样可以找到以该中心为中心的最长回文串。

两种情况

回文串有两种形态:

  • 奇数长度:中心是一个字符,如 “aba”,中心是 ‘b’
  • 偶数长度:中心是两个字符,如 “abba”,中心是 ‘bb’

代码实现

classSolution{public:stringlongestPalindrome(string s){intn=s.size();intans_l=0,ans_r=0;// 记录最长回文子串的左右边界// 情况1:奇数长度回文,中心是单字符for(inti=0;i<n;i++){intl=i,r=i;// 以 i 为中心// 向两边扩展,直到不匹配while(l>=0&&r<n&&s[l]==s[r]){l--;r++;}// 此时 [l+1, r-1] 是以 i 为中心的回文串// 长度 = r - l - 1if(r-l-1>ans_r-ans_l){ans_l=l+1;ans_r=r;}}// 情况2:偶数长度回文,中心是两个相邻字符for(inti=0;i<n-1;i++){intl=i,r=i+1;// 以 i 和 i+1 为中心while(l>=0&&r<n&&s[l]==s[r]){l--;r++;}if(r-l-1>ans_r-ans_l){ans_l=l+1;ans_r=r;}}returns.substr(ans_l,ans_r-ans_l);}};

二、算法流程图

以 s = “babad” 为例

原始字符串:b a b a d 索引: 0 1 2 3 4 第一步:枚举所有可能的回文中心 奇数长度中心(每个字符): i=0: "b" -> 向两边扩:l=0,r=0 -> s[0]==s[0],继续 l=-1,r=1 -> 超界,停止 回文:"b",长度 1 i=1: "a" -> 向两边扩:s[1]==s[1] -> l=0,r=2 s[0]==s[2] -> l=-1,r=3 -> 超界,停止 回文:"bab",长度 3 i=2: "a" -> 同上,回文:"aba",长度 3 i=3: "a" -> 向两边扩:s[3]==s[3] -> l=2,r=4 s[2]!=s[4],停止 回文:"a",长度 1 i=4: "d" -> 向两边扩:s[4]==s[4],停止 回文:"d",长度 1 偶数长度中心(相邻字符对): i=0: "ba" -> s[0]!=s[1],停止 回文:无 i=1: "ab" -> s[1]!=s[2],停止 回文:无 i=2: "ba" -> s[2]!=s[3],停止 回文:无 i=3: "ad" -> s[3]!=s[4],停止 回文:无 最长回文:"bab" 或 "aba",长度 3

以 s = “cbbd” 为例

原始字符串:c b b d 索引: 0 1 2 3 奇数长度中心: i=0: "c" -> 长度 1 i=1: "b" -> 向两边扩:s[1]==s[1] -> l=0,r=2 s[0]!=s[2],停止 回文:"b",长度 1 i=2: "b" -> 同上,回文:"b",长度 1 i=3: "d" -> 长度 1 偶数长度中心: i=0: "cb" -> s[0]!=s[1],停止 i=1: "bb" -> 向两边扩:s[1]==s[2] -> l=0,r=3 s[0]!=s[3],停止 回文:"bb",长度 2 i=2: "bd" -> s[2]!=s[3],停止 最长回文:"bb",长度 2

三、逐行解析(对照原题代码)

stringlongestPalindrome(string s){intn=s.size();// ans_l 和 ans_r 记录当前找到的最长回文子串的左右边界(左闭右开)intans_l=0,ans_r=0;// ---------- 情况1:奇数长度回文 ----------// 中心是单字符,枚举每个位置作为中心for(inti=0;i<n;i++){intl=i,r=i;// 初始中心是字符 s[i]// while 循环:只要 l 和 r 都在范围内,且 s[l] == s[r],就继续扩展while(l>=0&&r<n&&s[l]==s[r]){l--;r++;}// 退出循环时:[l+1, r-1] 是以 i 为中心的最长回文串// 长度 = r - l - 1// 如果比当前答案更长,就更新if(r-l-1>ans_r-ans_l){ans_l=l+1;ans_r=r;}}// ---------- 情况2:偶数长度回文 ----------// 中心是两个相邻字符,枚举每对相邻字符for(inti=0;i<n-1;i++){intl=i,r=i+1;// 初始中心是 s[i] 和 s[i+1]while(l>=0&&r<n&&s[l]==s[r]){l--;r++;}if(r-l-1>ans_r-ans_l){ans_l=l+1;ans_r=r;}}// 返回从 ans_l 开始,长度为 ans_r - ans_l 的子串returns.substr(ans_l,ans_r-ans_l);}

关键点解释

语句含义
int ans_l = 0, ans_r = 0;记录最长回文子串的边界,ans_l 是起始索引,ans_r 是结束索引(不包含)
while (l >= 0 && r < n && s[l] == s[r])扩展条件:左边界没越界,右边界没越界,左右字符相等
l--; r++;向两边扩展一位
ans_l = l + 1; ans_r = r;更新答案,l+1 是因为退出循环时 l 多减了 1
r - l - 1当前回文串的长度
s.substr(ans_l, ans_r - ans_l)C++ 的 substring,参数是起始位置和长度

四、图解扩展过程

以 s = "babad",i = 1(中心是 'a')为例: 初始状态: l=i=1, r=i=1 b a b a d ^ 中心 第一次扩展(l=0, r=2): s[0] == s[2]? 'b' == 'b'? 是! b a b a d ^ ^ l r 第二次扩展(l=-1, r=3): l < 0,越界,停止 最终回文:[l+1, r-1] = [0, 2] = "bab" 长度 = r - l - 1 = 3 - (-1) - 1 = 3
以 s = "cbbd",i = 1(中心是 "bb")为例: 初始状态: l=i=1, r=i+1=2 c b b d ^^ 中心 第一次扩展(l=0, r=3): s[0] == s[3]? 'c' == 'd'? 否,停止 最终回文:[l+1, r-1] = [1, 2] = "bb" 长度 = r - l - 1 = 4 - 0 - 1 = 3? 错! 实际是 [1,3) = s[1] 和 s[2],长度 = 2 注意:退出 while 时 l=-1, r=4 回文边界是 [l+1, r-1] = [0, 3],长度 = 3? 不对 实际 l=0 时已经判断 s[0]!=s[3],所以回文不包括 0 和 3 正确:[1, 3) 不包括 r

五、复杂度分析

维度分析
时间复杂度最坏情况下,每个中心都要扩展 O(n) 次,共 O(n) 个中心,总 O(n^2)
空间复杂度只用了几个整数变量,O(1)

最坏情况

字符串是全相同字符,如 “aaaaa…”:

  • 奇数中心:每个扩展 O(n) 次
  • 偶数中心:每个扩展 O(n) 次
  • 总计 O(n^2)

六、动态规划解法(对比)

思路

定义dp[i][j]= s[i…j] 是否是回文串。

状态转移

  • dp[i][j] = (s[i] == s[j]) && dp[i+1][j-1]
  • 即首尾相等,且中间也是回文
classSolution{public:stringlongestPalindrome(string s){intn=s.size();if(n<=1)returns;vector<vector<int>>dp(n,vector<int>(n,0));intstart=0,maxLen=1;// 所有单字符都是回文for(inti=0;i<n;i++)dp[i][i]=1;// 枚举长度for(intlen=2;len<=n;len++){for(inti=0;i+len<=n;i++){intj=i+len-1;if(s[i]==s[j]){if(len==2)dp[i][j]=1;// "aa" 这种elsedp[i][j]=dp[i+1][j-1];}if(dp[i][j]&&len>maxLen){start=i;maxLen=len;}}}returns.substr(start,maxLen);}};

两种方法对比

维度中心扩展动态规划
时间复杂度O(n^2)O(n^2)
空间复杂度O(1)O(n^2)
编码难度简单中等
思维难度易理解需理解 DP 状态定义

七、Manacher 算法(了解即可)

核心思想

O(n) 时间复杂度的算法,利用回文串的对称性避免重复计算。

// 伪代码,不完整实现stringmanacher(string s){// 1. 插入分隔符,使奇偶统一string t="#";for(charc:s){t+=c;t+="#";}// 2. 计算每个位置的回文半径vector<int>p(t.size(),0);intcenter=0,right=0;for(inti=0;i<t.size();i++){intmirror=2*center-i;if(i<right)p[i]=min(p[mirror],right-i);// 中心扩展while(i-p[i]>=0&&i+p[i]<t.size()&&t[i-p[i]]==t[i+p[i]]){p[i]++;}// 更新 center 和 rightif(i+p[i]>right){center=i;right=i+p[i];}}// 3. 找出最长回文// ...}

面试中如果能提到 Manacher,可以加分,但中心扩展已经足够。


面试追问 FAQ

问题回答要点
Q: 为什么中心扩展能找所有回文子串?任何回文串都有中心(单字符或双字符),枚举所有可能的中心,扩展找最长
Q: 如何处理奇数和偶数两种情况?分别处理:奇数中心是单字符,偶数中心是相邻字符对
Q: 时间复杂度为什么是 O(n^2)?有 O(n) 个中心,每个中心最多扩展 O(n) 次
Q: 能用滑动窗口吗?不行,因为回文长度不确定,无法用滑动窗口优化
Q: 如果要返回所有最长回文怎么办?在遍历过程中记录所有等长的回文,而不是只更新一个
Q: Manacher 算法了解吗?可以简单说:O(n) 算法,利用回文对称性,用半径数组避免重复计算

相关题目

题目编号题目名称难度核心差异
5最长回文子串中等基础题,返回子串
516最长回文子序列中等返回长度或子序列,不要求连续
647回文子串中等计数所有回文子串
409最长回文串简单可以重新排列字符
1312让字符串成为回文串的最少插入困难插入最少字符使字符串回文

总结

要点内容
核心思想中心扩展:枚举每个可能的中心(单字符或双字符),向两边扩展找最长
两种情况奇数长度(单中心)和偶数长度(双中心)
时间复杂度O(n^2)
空间复杂度O(1)
关键点注意边界条件,while 循环结束后 l 多减了 1
易错点偶数中心时初始化是 l=i, r=i+1,不是 l=i-1, r=i+1

中心扩展法是最直观、最实用的解法,面试中推荐使用。如果面试官问更优解,可以补充 Manacher 算法的思想。


http://www.jsqmd.com/news/896564/

相关文章:

  • 基于4T2M TCAM的无损软PUF设计:硬件安全新范式
  • 安培环路定律|磁路计算核心公式 + 工程应用
  • 多人协作表格哪个好用?2026年最新工具答案来了
  • 2026年4月镀锌管采购攻略:精选厂家一览,20#无缝钢管/薄壁精密钢管/异型管/厚壁无缝钢管/方管,镀锌管公司推荐 - 品牌推荐师
  • 2026降AI率工具红黑榜:AI智能降重工具怎么选?清单来了 - 降AI小能手
  • 毕业答辩效率神器|告别熬夜改PPT,百考通AI一站式搞定答辩演示文稿
  • 魔兽世界API与宏命令工具:终极免费指南与实用技巧
  • 国际机票代理哪家强?实测3家龙头:第一名武汉圣擎,售后无人能及! - 土星买买买
  • 如何快速完成音频格式转换:免费工具FlicFlac的完整指南
  • 2026年反渗透水处理设备厂家怎么选?标杆企业全景洞察与应用深度解析 - 深度智识库
  • 告别笨重的串口助手:用SEGGER RTT Viewer实时抓取单片机日志的完整配置流程
  • 从‘unwrap’函数到三维点云:Matlab四步相移条纹三维重建全流程拆解
  • 保姆级教程:在Ubuntu 22.04上用SCons为CanMV K230大小核交叉编译CoreMark(附完整SConstruct文件)
  • 2026济宁市本地人必选的公共卫生检测专业机构TOP5推荐!美容院、足疗店、酒店宾馆卫生检测、许可证办理,正规CMA资质检测公司排名推荐 (2026年5月商铺卫生办证最新深度调研方案) - 防水补漏3
  • 3个被忽略的习惯断点,正在悄悄废掉你的ChatGPT生产力:即刻启用「Prompt-Action-Review」三阶追踪表
  • 3步搞定Nginx配置美化:新手也能快速上手的终极指南
  • STM32CubeMX实战指南:定时器中断精准控制与多场景应用
  • Windows软件测试员的效率神器:用Python uiautomation + Inspect.exe实现‘所见即所得’的控件抓取与回放
  • 基于MCP协议自建DORA指标仪表盘:从数据驱动到效能闭环
  • 【他山之石】《被讨厌的勇气》导读
  • 从问答到执行:Claude Code如何实现一键式智能安全审计
  • 使用容器提供postgresql RESTful API服务 - Fan
  • 如何用novelWriter提升小说创作效率:开源结构化写作工具终极指南
  • 毕业答辩高效通关:用百考通AI 30分钟搞定专业答辩PPT
  • 构建容错性强的AI应用时如何借助Taotoken的路由与容灾能力
  • harness与hermes-agent的区别
  • STM32F103定时器入门:从CubeMX配置到代码实战,5分钟搞懂TIM2时钟源设置
  • 别再死记硬背了!用这3个真实项目案例,帮你彻底搞懂PERT图、关键路径和浮动时间
  • 别再手动导数据了!用SeaTunnel 2.3.1把Hive数据自动同步到StarRocks(附完整配置文件)
  • 告别手动测试!用CPAL脚本的IL函数实现CAN总线自动化故障注入