《字符串相亲记:如何在 O(n²) 内找到你的“完美镜像“?》
《字符串相亲记如何在 O(n²) 内找到你的完美镜像》又名最长回文子序列——一个让字符串自我欣赏的算法一、引子当字符串开始自恋话说在字符串王国里每个字符串都有一个终极梦想——成为回文。什么叫回文就是那种正着读、反着读都一样的字符串比如level、noon、上海自来水来自海上中文乱入。但不是每个字符串都能天生丽质。比如bbbab这个倒霉蛋它想当回文但中间那个a像个电灯泡一样杵在那里破坏了整体的和谐感。于是它想能不能删几个字符让我变成一个回文这就是我们今天的主角——最长回文子序列Longest Palindromic Subsequence。二、什么是子序列别急先吃个汉堡在讲算法之前必须先搞清楚一个概念子序列。假设你有一个汉堡配料依次是[面包, 生菜, 牛肉, 番茄, 面包]。子序列的意思是你可以不吃某些配料但剩下配料的顺序不能变。比如你可以吃[面包, 牛肉, 面包]中间的生菜和番茄扔了这算一个子序列。但你不能变成[牛肉, 面包, 生菜]因为顺序乱了——这就不是子序列而是打乱顺序的黑暗料理了。回到bbbab它的子序列有bbb删掉最后一个a和最后一个bbbbb删掉那个碍眼的abab精简约会版其中最长的回文子序列就是bbbb长度为 4。三、解法一二维 DP——填格子的艺术3.1 核心思想区间 DP我们定义dp[i][j]为字符串s[i...j]这个区间内的最长回文子序列长度。注意这里的关键是区间。我们要解决一个大问题整个字符串先解决一堆小问题所有小区间然后用小问题的答案拼出大问题的答案。3.2 状态转移相爱相杀的两种命运现在我们盯着区间s[i...j]的两端命运 A两端的字符一见钟情s[i] s[j]比如s bbbab区间[0, 4]两端都是b。那太好了这两个b可以手拉手加入回文队伍。此时dp[i][j] dp[i1][j-1] 2意思是中间部分[i1, j-1]能凑出多长的回文再加上我们俩这 2 个字符。命运 B两端的字符相看两厌s[i] ! s[j]比如区间[0, 3]两端是b和a不匹配。那怎么办只能二选一要么去掉左边的b看[1, 3]能搞多长要么去掉右边的a看[0, 2]能搞多长取两者最大值dp[i][j] max(dp[i1][j], dp[i][j-1])3.3 代码登场classSolution{publicintlongestPalindromeSubseq(Strings){intns.length();int[][]dpnewint[n][n];// 对角线初始化单个字符本身就是回文长度为1for(inti0;in;i)dp[i][i]1;// i 从下到上遍历为什么因为 dp[i][j] 依赖 dp[i1][...]for(intin-1;i0;i--){for(intji1;jn;j){if(s.charAt(i)s.charAt(j)){dp[i][j]dp[i1][j-1]2;}else{dp[i][j]Math.max(dp[i1][j],dp[i][j-1]);}}}returndp[0][n-1];}}3.4 填表过程可视化以bbbab为例填完表长这样0(b) 1(b) 2(b) 3(a) 4(b) 0 [ 1 2 3 3 4 ] 1 [ - 1 2 2 3 ] 2 [ - - 1 1 3 ] 3 [ - - - 1 1 ] 4 [ - - - - 1 ]右上角dp[0][4] 4就是答案。复杂度时间 O(n²)空间 O(n²)。优点是思路清晰缺点是空间有点大——就像你租房租了个三居室其实一个人睡就够了。四、解法二一维 DP——断舍离的空间优化面试官看完后点点头“能不能优化一下空间”你微微一笑“可以用滚动数组。”4.1 核心观察仔细看二维 DP 的状态转移dp[i][j]只依赖于dp[i1][j-1]左下角dp[i1][j]正下方dp[i][j-1]左边也就是说第i行只依赖于第i1行。那干嘛存整个二维数组用一维数组就够了4.2 变量们的变形记我们用dp[j]表示当前正在计算的第i行。但问题来了当我们更新dp[j]时需要用到dp[j]的旧值表示dp[i1][j]dp[j-1]的新值表示dp[i][j-1]刚刚算好的dp[i1][j-1]左下角这个最棘手前两个直接用数组就行但左下角怎么办用prev变量来保存classSolution{publicintlongestPalindromeSubseq(Strings){intns.length();int[]dpnewint[n];for(intin-1;i0;i--){dp[i]1;// 对角线初始化intprev0;// 保存 dp[i1][j-1]for(intji1;jn;j){inttempdp[j];// 先保存 dp[i1][j] 的旧值if(s.charAt(i)s.charAt(j)){dp[j]prev2;}else{dp[j]Math.max(dp[j],dp[j-1]);}prevtemp;// 下一轮dp[i1][j] 就变成 dp[i1][j-1] 了}}returndp[n-1];}}4.3 三步走战略temp dp[j]保存旧值dp[i1][j]计算dp[j]的新值dp[i][j]prev temp把旧值交给prev下一轮它就是左下角了这就像接力赛跑prev是接力棒一棒接一棒传下去。复杂度时间 O(n²)空间 O(n)。从三居室搬到单间生活照样精彩。五、解法三LCS 转化——“打不过就搬救兵”面试官推了推眼镜“还有别的思路吗”你胸有成竹“有转化为最长公共子序列问题。”5.1 一个神奇的等式最长回文子序列 原串 与 反转串 的最长公共子序列为什么因为回文串正读反读都一样。如果一个序列是回文那它在原串中是这样在反转串中也是这样——它就是两串的公共子序列比如bbbab反转后是babbb它们的公共子序列bbbb长度为 4。5.2 LCS 的状态转移定义dp[i][j]s[0..i-1]和t[0..j-1]的最长公共子序列长度。if(s[i-1]t[j-1])dp[i][j]dp[i-1][j-1]1;// 相等一起选elsedp[i][j]max(dp[i-1][j],dp[i][j-1]);// 不等二选一5.3 完整代码classSolution{publicintlongestPalindromeSubseq(Strings){StringtnewStringBuilder(s).reverse().toString();intns.length();int[][]dpnewint[n1][n1];for(inti1;in;i){for(intj1;jn;j){if(s.charAt(i-1)t.charAt(j-1)){dp[i][j]dp[i-1][j-1]1;}else{dp[i][j]Math.max(dp[i-1][j],dp[i][j-1]);}}}returndp[n][n];}}这个方法的妙处在于你学一个 LCS就白赚一道回文子序列血赚不亏。六、三种解法对比总结解法核心思想时间空间面试推荐度二维 DP区间动态规划O(n²)O(n²)⭐⭐⭐⭐⭐ 必会一维 DP滚动数组优化O(n²)O(n)⭐⭐⭐⭐ 加分项LCS 转化问题转化O(n²)O(n²)⭐⭐⭐⭐ 展示知识广度七、写在最后动态规划就像谈恋爱——先解决小问题再解决大问题。单个字符是回文长度为1→ 两个字符能匹配吗 → 三个字符呢 → 最后搞定整个字符串。每一步都依赖前面已经算好的结果就像每一段稳定的感情都建立在之前的经历之上。至于空间优化那就是学会断舍离——丢掉没用的东西只保留真正需要的。所以下次面试官问你这道题你可以自信地说“这道题我有三种解法。第一种是标准的区间 DP第二种优化到 O(n) 空间第三种转化为 LCS。您想听哪一种”然后看着面试官满意的微笑知道自己稳了。参考资料LeetCode 516. Longest Palindromic Subsequence《算法导论》第15章 动态规划如果这篇文章对你有帮助欢迎点赞收藏转发三连你的支持是我写下去的最大动力我们下期见拜拜

相关新闻

Surface设备利用Ventoy在TF卡上实现FydeOS多系统引导指南

Surface设备利用Ventoy在TF卡上实现FydeOS多系统引导指南

1. 项目概述:在Surface上实现TF卡启动FydeOS 如果你手头有一台微软的Surface设备,无论是Pro、Go还是Laptop系列,并且对Windows系统感到有些审美疲劳,或者想体验一下基于Chrome OS生态的轻量、快速、安全的FydeOS,但又…

2026/10/10 12:48:10 阅读更多 →
面对完全陌生的线上应用,我靠这套“找日志“方法论,10 分钟摸清家底

面对完全陌生的线上应用,我靠这套“找日志“方法论,10 分钟摸清家底

为什么"陌生应用排障"这么让人崩溃 我以前遇到一些项目,发现他们遇到对自己的应用了解很少: 点开服务器一看,进程名看不懂,目录结构乱七八糟,日志文件几十个,不知道该看哪个。 然后是乱。上来就 …

2026/10/5 8:50:58 阅读更多 →
Android面试题---ListView

Android面试题---ListView

1. ListView 如何定位到指定位置在 Android 开发中,我们经常需要将 ListView 滚动到特定位置,例如用户点击某个按钮后跳转到列表的指定项。ListView 提供了多种方法来实现精确定位。1.1 核心定位方法ListView 最常用的定位方法是 setSelection(int posit…

2026/10/10 18:43:31 阅读更多 →

最新新闻

STM32定时器寄存器操作:从点灯到精确计时的关键跨越

STM32定时器寄存器操作:从点灯到精确计时的关键跨越

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:25:06 阅读更多 →
飞控计算机硬实时操作系统:从确定性调度到适航认证

飞控计算机硬实时操作系统:从确定性调度到适航认证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:25:06 阅读更多 →
D-LinkNet:面向高分辨率卫星影像的轻量级语义分割方案

D-LinkNet:面向高分辨率卫星影像的轻量级语义分割方案

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:25:05 阅读更多 →
信息隐藏系统框架:面向工程落地的隐写分层架构

信息隐藏系统框架:面向工程落地的隐写分层架构

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:25:05 阅读更多 →
AnyPS5跨设备串流实战:编码参数、网络优化与输入回传调优指南

AnyPS5跨设备串流实战:编码参数、网络优化与输入回传调优指南

1. 项目缘起与核心定位AnyPS5 这个标题第一次出现在我视野里的时候,我下意识地把它拆成了两个部分来看——“Any”和“PS5”。前者代表通用、跨平台、不受限,后者则指向那个大家都很熟悉的游戏主机生态。把这两个词拼在一起,直觉告诉我&#…

2026/10/13 1:25:05 阅读更多 →
MinGW for Win64 编译与分发实战:从工具链选型到静态链接避坑

MinGW for Win64 编译与分发实战:从工具链选型到静态链接避坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/13 1:24:05 阅读更多 →

日新闻

物联网系统集成避坑指南:从选型到验收的关键要点

物联网系统集成避坑指南:从选型到验收的关键要点

做了这么多年物联网系统集成的项目,我最大的感受是,大部分坑不是出在技术前沿,而是出在一些看起来很平常的地方。设备选了便宜的、协议没问清楚、现场没摸透、验收标准写得含糊——这些细枝末节,最后都会变成延期、超支和扯皮的大…

2026/10/13 0:00:57 阅读更多 →
ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

ESP32舵机控制全攻略:从PWM原理到高级玩法与常见故障排查

如果你玩ESP32有一阵子了,很难不碰到舵机控制这道坎。做智能小车要转方向,做机械臂要抬关节,做云台要偏航俯仰,背后都是同一个东西在干活:舵机。而我见过太多人卡在“代码能编译、舵机就是不听话”这个阶段&#xff0c…

2026/10/13 0:00:57 阅读更多 →
CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

CAN总线原理与STM32实战:抗干扰、仲裁、错误处理全解析

1. 为什么CAN总线至今仍是工业现场的“硬通货”?——从汽车底盘到智能温室的真实需求你有没有拆过一辆老款燃油车的中控台?或者调试过某高校实验室里那台跑着Modbus协议却频频丢帧的PLC控制柜?又或者,在某个农业物联网项目里&…

2026/10/13 0:00:57 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/12 0:16:30 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/12 0:16:38 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/12 0:16:43 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 21:05:09 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 14:48:49 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/12 18:11:17 阅读更多 →