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

(huawei)5.最长回文子串

详解最长回文子串:中心扩展法源码拆解与实战分析(LeetCode 5)

在算法学习中,「最长回文子串」是一道绕不开的经典题目(LeetCode 第 5 题)。它不仅考察对回文特性的理解,更考验如何在效率与实现复杂度之间找到平衡。本文将基于 中心扩展法 的完整源码,从算法思想、代码拆解、实战验证到复杂度分析,一步步带你掌握这道题的最优解之一。

一、问题回顾:什么是“最长回文子串”?

首先明确问题边界:给定一个字符串 s,找到其中最长的回文子串(回文是指正读和反读都一样的字符串,如“aba”“aa”)。例如:

  • 输入 s = "babad",输出可以是 "bab""aba"
  • 输入 s = "cbbd",输出是 "bb"

常见解法有暴力法(O(n³) 时间,低效)、动态规划(O(n²) 时间+O(n²) 空间)、中心扩展法(O(n²) 时间+O(1) 空间)和 Manacher 算法(O(n) 时间,实现较复杂)。本文的中心扩展法,是兼顾 代码简洁性空间效率 的优选方案,尤其适合面试场景快速手写。

二、算法核心思想:利用回文的“对称性”

回文的本质是“左右对称”——以某个“中心”为轴,两边的字符完全相同。因此,我们可以:

  1. 枚举所有可能的中心
    • 奇数长度回文(如“aba”):中心是单个字符(例中“b”);
    • 偶数长度回文(如“aa”):中心是两个相邻字符(例中两个“a”)。
  2. 向左右扩展验证:对每个中心,不断向左右两边延伸,检查两边字符是否相等,直到越界或字符不相等;
  3. 记录最长结果:在扩展过程中,实时更新“最长回文子串的长度”和“起始位置”,最终通过起始位置和长度提取结果。

三、源码逐行拆解:从变量到逻辑

先贴出完整源码(与你提供的一致),再逐部分解析:

class Solution {
public:string longestPalindrome(string s) {// 1. 初始化变量int left = 0, right = 0, max_cnt = 0, res_start = 0;// 2. 遍历每个字符,作为回文中心for (int i = 0; i < s.size(); i++) {// 3. 情况1:以单个字符 i 为中心(对应奇数长度回文,如 "aba")left = i;right = i;while (left > -1 && right < s.size() && s[left] == s[right]) {// 更新最长回文的长度和起始位置if (right - left + 1 > max_cnt) {max_cnt = right - left + 1;res_start = left;}// 向左右扩展left--;right++;}// 4. 情况2:以 i 和 i+1 为中心(对应偶数长度回文,如 "aa")left = i;right = i + 1;while (left > -1 && right < s.size() && s[left] == s[right]) {// 更新最长回文的长度和起始位置if (right - left + 1 > max_cnt) {max_cnt = right - left + 1;res_start = left;}// 向左右扩展left--;right++;}}// 5. 提取并返回最长回文子串return s.substr(res_start, max_cnt);}
};

3.1 变量初始化:4个核心变量的作用

int left = 0, right = 0, max_cnt = 0, res_start = 0;
  • left/right:扩展时的左右指针,分别指向当前回文的左边界和右边界;
  • max_cnt:记录最长回文子串的长度(初始为0,后续动态更新);
  • res_start:记录最长回文子串的起始索引(初始为0,配合max_cnt最终提取结果)。

3.2 遍历中心:for循环的意义

for (int i = 0; i < s.size(); i++) { ... }
  • 遍历字符串的每个字符 s[i],将其作为“潜在中心”的起点;
  • 为什么用 s.size()?因为要遍历到最后一个字符(i 最大为 s.size()-1),避免遗漏任何可能的中心;
  • 注意:s.size() 返回 size_t 类型(无符号整数),但这里 iint,在字符串长度不超过 int 最大值时(日常场景均满足),无需担心类型问题。

3.3 两种扩展场景:覆盖所有回文类型

场景1:单个字符为中心(奇数长度回文)
left = i;
right = i;
while (left > -1 && right < s.size() && s[left] == s[right]) {// 更新最长回文信息if (right - left + 1 > max_cnt) {max_cnt = right - left + 1;res_start = left;}// 扩展left--;right++;
}
  • 初始时 left = right = i:以 s[i] 为中心,此时回文长度为1(单个字符本身就是回文);
  • while 循环条件解析(三个条件必须同时满足):
    1. left > -1:左指针不越界(避免访问 s[-1]);
    2. right < s.size():右指针不越界(避免访问 s[s.size()],超出字符串长度);
    3. s[left] == s[right]:左右字符相等,满足回文条件;
  • 内部更新逻辑:right - left + 1 是当前回文的长度,若比 max_cnt 大,说明找到更长的回文,更新 max_cntres_start(起始位置是 left,因为 left 是当前回文的左边界);
  • 扩展操作:left--(左移)、right++(右移),继续验证下一对字符。
场景2:两个相邻字符为中心(偶数长度回文)
left = i;
right = i + 1;
while (left > -1 && right < s.size() && s[left] == s[right]) {// 同场景1:更新最长回文信息if (right - left + 1 > max_cnt) {max_cnt = right - left + 1;res_start = left;}// 扩展left--;right++;
}
  • 初始时 left = iright = i+1:以 s[i]s[i+1] 为中心,此时回文长度为2(需满足 s[i] == s[i+1] 才是回文);
  • 其余逻辑与场景1完全一致,目的是覆盖偶数长度的回文(如“aa”“abba”)。

3.4 结果提取:substr的妙用

return s.substr(res_start, max_cnt);
  • std::string::substr(pos, len) 函数:从索引 pos 开始,提取长度为 len 的子串;
  • 例如:若 s = "babad",最终 res_start = 0max_cnt = 3,则 substr(0, 3) 返回 "bab",正是最长回文子串。

四、实战示例:代码如何运行?

以输入 s = "cbbd" 为例,一步步看代码执行过程:

  1. 初始化left=0, right=0, max_cnt=0, res_start=0
  2. i=0(s[0]=‘c’)
    • 场景1:left=0, right=0,回文长度1 > 0 → max_cnt=1, res_start=0;扩展后 left=-1,循环停止;
    • 场景2:left=0, right=1(s[0]=‘c’ vs s[1]=‘b’,不相等),循环不执行;
  3. i=1(s[1]=‘b’)
    • 场景1:left=1, right=1,回文长度1(不大于max_cnt=1),无更新;扩展后 left=0(‘c’)vs right=2(‘b’),不相等,停止;
    • 场景2:left=1, right=2(s[1]=‘b’ vs s[2]=‘b’,相等),回文长度2 > 1 → max_cnt=2, res_start=1;扩展后 left=0(‘c’)vs right=3(‘d’),不相等,停止;
  4. i=2(s[2]=‘b’)
    • 场景1:left=2, right=2,长度1 < 2,无更新;扩展后 left=1(‘b’)vs right=3(‘d’),不相等;
    • 场景2:left=2, right=3(‘b’ vs ‘d’,不相等),无执行;
  5. i=3(s[3]=‘d’)
    • 场景1:长度1 < 2,无更新;
    • 场景2:right=4(超出s.size()=4),循环不执行;
  6. 返回结果substr(1, 2)"bb",正确!

五、复杂度分析:效率如何?

  • 时间复杂度 O(n²)
    遍历每个字符(O(n)),每个字符对应两次扩展(场景1和场景2),每次扩展最多遍历到字符串两端(O(n)),因此总时间为 O(n×n) = O(n²);
  • 空间复杂度 O(1)
    仅使用了 leftrightmax_cntres_start 4个变量,无额外数据结构(如数组、DP表),空间开销与字符串长度无关。

六、注意事项与优化建议

  1. 边界处理
    • 若字符串长度为1(如 s = "a"),代码会直接返回 "a"max_cnt=1res_start=0),无需额外判断;
    • 若字符串为空(s = ""),返回空串(max_cnt=0substr(0,0) 为空)。
  2. 为什么不用 at() 访问字符?
    s.at(pos) 会做越界检查,越界时抛出 out_of_range 异常;而 s[pos] 无检查,更高效。本文代码已通过 left > -1right < s.size() 手动控制边界,无需 at()
  3. 与动态规划的对比
    动态规划(DP)通过 dp[i][j] 记录 s[i..j] 是否为回文,空间复杂度 O(n²);中心扩展法空间 O(1),实现更简洁,面试中更推荐手写。

七、总结

中心扩展法解决最长回文子串问题,核心是利用回文的对称性枚举所有中心,通过“扩展-验证-更新”的流程找到最长回文。其优势在于:

  • 代码简洁,逻辑直观,10分钟内可手写完成;
  • 空间复杂度 O(1),无额外内存开销;
  • 覆盖所有回文类型(奇数/偶数长度),无遗漏。

如果你是算法新手,建议先理解“中心枚举”的思想,再逐行调试代码(比如用 s = "babad"s = "cbbd" 测试),就能轻松掌握这道经典题!

http://www.dtcms.com/a/545148.html

相关文章:

  • 跨厂商(华为 H3C)防火墙 IPSec 隧道部署
  • 在nestjs中集成jwt校验
  • 环球资源网商务网站建设目的自己怎么做网页推广
  • (第三篇)Spring AI 基础入门:PromptTemplate 与对话工程实战(从字符串拼接到底层模板引擎的进阶之路)
  • les做ml网站template是什么意思
  • Node.js环境变量配置实战:安全高效开发指南
  • 了解学习Keepalived双机热备
  • 欧美网站建设教程seo排名优化点击软件有哪些
  • 如何通过网站标题找网站百度做公司网站
  • STL容器string的模拟实现
  • X-AnyLabeling 开启 ultralytics GPU训练模式
  • Linux进程:进程状态
  • 网站建设之婚礼摄影网站设计ppt模板免费下载 素材学生版
  • 用html5做手机网站北京在建项目查询
  • Go语言设计模式:适配器模式详解
  • 电商食品网站建设南宁网红打卡
  • C 文件读写
  • 如何获取npm的认证令牌token
  • freeRTOS学习笔记(十二)--信号量
  • BLIP 系列全解析与深度面经:从视觉语言统一到跨模态对齐的演进
  • TCP 和 UDP 的核心区别:从原理到场景的全面解析
  • 做外贸网站基本流程wordpress d8 4.1
  • Backend - HTTP请求的常用返回类型(asp .net core MVC)
  • 国内最大的网站制作公司免费创建属于自己的网站
  • [人工智能-大模型-103]:模型层 - M个神经元组成的单层神经网络的本质
  • 【面试题】缓存先删后写如何避免窗口期的旧数据写入缓存
  • 扩展名网站最新新闻事件摘抄
  • 网站免费推广方法网站正能量免费推广软件
  • Spring Boot3零基础教程,配置 GraalVM 环境,笔记88
  • TCN-Transformer-LSTM多特征分类预测Matlab实现