LeetCode 1218「最长定差子序列」题解:用值域哈希表把 O(N²) 暴力优化到 O(n) 动态规划
LeetCode 1218「最长定差子序列」题解用值域哈希表把 O(N²) 暴力优化到 O(n) 动态规划【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode本篇基于 leetcode 题解仓库中的1218 最长定差子序例题解完整还原这道动态规划题的求解路径先给出题目与约束再用 O(N²) 暴力枚举建立直觉随后推导以数值为键的哈希表 DP 并将复杂度压到 O(n)文中还通过对示例的逐步推演验证了重复值场景下的正确性并指出 difference 0 这一边界下原写法会低估结果、给出修正实现最后把该值域 DP模式推广到仓库中同源的 3041 题帮助读者掌握dp[数值] 最长链长这一通用套路。一、题目与约束条件原题1218. 最长定差子序列力扣中等难度题目解标注的面试出处为腾讯给你一个整数数组 arr 和一个整数 difference请你找出 arr 中所有相邻元素之间的差等于给定 difference 的等差子序列并返回其中最长的等差子序列的长度。题目解原文给出的三组示例示例 1输入arr [1,2,3,4], difference 1输出4解释最长的等差子序列是[1,2,3,4]。示例 2输入arr [1,3,5,7], difference 1输出1解释最长的等差子序列是任意单个元素。示例 3输入arr [1,5,7,8,5,3,4,2,1], difference -2输出4解释最长的等差子序列是[7,5,3,1]。约束条件决定算法选型的关键信息1 arr.length 10^5 -10^4 arr[i], difference 10^4从约束中可以提炼出两点题目要求的是子序列subsequence而非子数组subarray元素在原数组中不必连续但必须保持原有先后顺序数组长度最高 10^5而元素值域只有[-10^4, 10^4]共 20001 个可能取值——值域远小于长度这正是后文用数值而不是下标作为 DP 状态键的依据。题目解标注的前置知识为数组与动态规划。二、暴力法枚举起点向后扫描题目解给出的最直观思路是暴力枚举出以每一个元素为开始元素的所有情况也就是对所有起点全部模拟一遍——这是暴力法的精髓先保证完备。这种解法会 TLE超时但顺着这个思维继续思考才能自然地引出优化方向。暴力法代码题目解原文Python3def longestSubsequence(self, arr: List[int], difference: int) - int: n len(arr) res 1 for i in range(n): count 1 for j in range(i 1, n): if arr[i] difference * count arr[j]: count 1 if count res: res count return res思路拆解对每个起点i令count记录从arr[i]出发已经接上的链长向后扫描j当且仅当arr[j]恰好等于等差链的第count项即arr[i] difference * count时count 1。由于链上的每一项取值都是确定的扫描遇到不匹配的元素只是跳过不影响后续匹配因此对每个起点都得到了一条最长定差子序列的长度取全局最大值res。复杂度题目解标注时间复杂度 $O(N^2)$、空间复杂度 $O(N)$。就这份实现而言两层循环共约 $N^2/2$ 次迭代当 $N 10^5$ 时达到 $10^{10}$ 量级必然超时另外从实现细节看代码额外使用的变量只有res、count与循环变量辅助空间实际上是 $O(1)$。三、动态规划把状态键从下标换成数值状态定义与转移方程题目解的核心想法是将以每一个元素结尾的最长等差子序列的长度统统存起来即dp[num] maxLen。这样遍历到一个新元素时就去之前的存储中查找dp[num - difference]如果找到了就更新dp[num] dp[num - difference] 1否则不做操作保持默认值 1。形式化地写状态dp[num]表示以值为 num 的元素结尾的最长定差子序列长度只保留以该值结尾的所有链中的最长者转移按原数组顺序扫描到num时dp[num] dp[num - difference] 1若num - difference此前出现过否则dp[num] 1答案max(dp.values())。为什么可以只按值记忆而丢掉下标链的合法性只依赖两个条件数值上恰好等于前驱值 difference以及前驱在数组中先出现。由于我们是从左到右单次扫描、遇到元素就立刻写表字典里任何一项dp[num - difference]都必然来自当前下标之前的某个位置顺序约束被按序扫描 查历史表这一结构天然满足无需在状态中记录下标。为什么能从 O(N²) 降到 O(n)这正是题目解所说的空间换时间暴力法中每个起点都要重新向后扫一遍每个状态被反复计算而哈希表把以某值结尾的最长链长缓存下来之后每个新元素只需一次 $O(1)$ 查表即可确定转移总时间 $O(n)$。这与仓库动态规划长文动态规划套路中状态转移 记忆化的主线一致把子问题的解存成表后续状态只做查表与更新。用示例 3 逐步推演以arr [1,5,7,8,5,3,4,2,1]、difference -2为例。此时查询键为num - difference num 2逐元素执行先置 1命中则覆盖为dp[num2] 1遍历元素 num查询键num 2查询结果更新后 dp[num]当前最大值13未命中dp[1] 1157未命中dp[5] 1179未命中dp[7] 11810未命中dp[8] 1157命中dp[7] 1dp[5] 2235命中dp[5] 2dp[3] 3346未命中dp[4] 1324命中dp[4] 1dp[2] 2313命中dp[3] 3dp[1] 44最终max(dp.values()) 4对应的正是解释中的[7,5,3,1]原数组下标 2、4、5、8。注意第二个5把dp[5]从 1 刷新为 2、随后的3又继承了这个 2——链是随着扫描逐步生长的这正是按序扫描 查历史表的威力每个元素只被处理一次却能把此前所有前驱的功劳都接过来。四、完整代码题目解给出的 Python3 实现原文保留class Solution: # 动态规划 def longestSubsequence(self, arr: List[int], difference: int) - int: n len(arr) res 1 dp {} for num in arr: dp[num] 1 if num - difference in dp: dp[num] dp[num - difference] 1 return max(dp.values())代码逐行说明dp {}以数值为键的哈希表即题目解强调的关键点——把以每一个元素结尾的最长等差子序列的长度统统存起来dp[num] 1每个元素先按自己单独成链初始化if num - difference in dp只有当前驱值确实先出现过时才能接链注意这里查的是num - difference而不是num difference——因为以 num 结尾的链其前一项的值必须是 num 减去公差return max(dp.values())最长链可能以任何一个值结尾取全部状态的极大值。五、复杂度分析与边界验证题目解给出的复杂度分析令 n 为数组长度时间复杂度$O(n)$——单次遍历每步两次 $O(1)$ 哈希操作空间复杂度$O(n)$——dp最多存 n 个键结合值域约束[-10^4, 10^4]实际键数上界为 $\min(n, 20001)$。边界 1重复值覆盖是否安全代码对同一个值反复执行先置 1、命中再覆盖会不会把之前算出的更长链抹掉不会。从实现结构看字典只增不删一旦num - difference进入过dp后续任何时刻它都还在且其值只增不减因此后一次对dp[num]的覆盖结果dp[num - difference] 1必然不小于前一次的值。先置 1在语义上只承担默认值的角色等价于dp[num] max(1, dp[num - difference] 1)的查表写法重复出现只会让链变得更长例如公差为 1 时重复的小数值会让后继元素接上更高的起点。边界 2difference 0 时原写法会低估修正写法约束允许difference 0此时答案应等于出现次数最多的值的次数。逐行推演题目解原代码在arr [3,3,3,0,3]、difference 0上的行为遍历元素执行过程dp 状态3第 1 个dp[3] 1查询键3 - 0 3刚被写入、必然命中 →dp[3] 2{3: 2}3第 2 个dp[3] 1被重置再命中自身 →dp[3] 2{3: 2}3第 3 个同上{3: 2}0dp[0] 1命中自身 →dp[0] 2{3: 2, 0: 2}3第 4 个dp[3] 1被重置再命中自身 →dp[3] 2{3: 2, 0: 2}原代码返回max 2而正确答案是4子序列[3,3,3,3]。原因在于difference 0时查询键就是num本身而代码先执行了dp[num] 1的无条件重置把正在累计的计数清零了。一个更稳健的等价写法是把重置与转移合并成一次赋值读取先于写入class Solution: def longestSubsequence(self, arr: List[int], difference: int) - int: dp {} for num in arr: dp[num] dp.get(num - difference, 0) 1 return max(dp.values())该写法在difference ≠ 0时与原解行为一致num - difference的键只会单调增长覆盖不会变差在difference 0时则退化为对每个值的自然计数dp[num] dp[num] 1两个场景都正确。六、模式推广同一套值域 DP在 3041 上的变体题目解把 3041. 修改数组后最大化数组中的连续元素数目 列为相关题目而该题解开头明确写道和 1218 类似——这正是本模式的一个变体同样将以每一个元素结尾的最长链长统统存起来dp[num]查询dp[num - 1]即公差固定为 1的特例不同点一3041 要求选出元素排序后连续对原顺序没有要求因此可以先排序再扫描不同点二由于元素可以至多 1当前元素既可当作链尾、也可当作链尾的前一项所以题解在更新dp[num]之外还要额外维护dp[num 1] memo[num] 1才能保证状态完备复杂度瓶颈变为排序时间 $O(n \log n)$、空间 $O(n)$。对照两题可以看出本套路的抽象形态当转移关系是值 → 值而非位置 → 位置、且值域可哈希时用哈希表按值存dp把双层循环压缩成单次扫描。1218 中公差任意、顺序敏感所以按原序扫描即可3041 中公差为 1、顺序不敏感所以排序后扫描并补一个前向一格的状态。七、该题解在仓库中的位置题解正文problems/1218.longest-arithmetic-subsequence-of-given-difference.md其章节组织题目地址 / 题目描述 / 前置知识 / 公司 / 思路 / 关键点解析 / 代码 / 相关题目与仓库题解模板要求的格式一致收录位置1218 作为中等难度题被收录在中等难度题目清单中1218. 最长定差子序列条目交叉引用3041 题解的相关题目一节反向链接回 1218两篇题解互相印证同一模式理论背景仓库中的动态规划长文对状态、转移、记忆化递归与 DP 的关系做了系统讲解可作为本篇第三节的延伸阅读。八、关键点小结暴力法枚举每个起点向后扫描等差链完备但 $O(N^2)$在 $N 10^5$ 约束下必然 TLE状态设计dp[num] 以值为num的元素结尾的最长定差子序列长度——利用值域远小于长度与转移只依赖值两个特点把状态键从下标换成数值转移方向查的是dp[num - difference]前驱值按原数组顺序单次扫描即可保证顺序约束复杂度时间 $O(n)$、空间 $O(n)$键数上界 $\min(n, 20001)$边界提醒difference 0时先置 1 再转移的写法会重置计数导致低估推荐合并为dp[num] dp.get(num - difference, 0) 1套路迁移同模式可直接套用到 3041公差固定为 1、可先排序、需额外维护dp[num 1]。【免费下载链接】leetcodeLeetCode Solutions: A Record of My Problem Solving Journey.( leetcode题解记录自己的leetcode解题之路。)项目地址: https://gitcode.com/gh_mirrors/le/leetcode创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

相关新闻

Wireshark实战:MQTT协议抓包分析与排障全指南

Wireshark实战:MQTT协议抓包分析与排障全指南

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

2026/9/20 10:48:33 阅读更多 →
wagmi Tempo 黑名单策略管理实战:`policy.useModifyBlacklist` Hook 使用与原理详解

wagmi Tempo 黑名单策略管理实战:`policy.useModifyBlacklist` Hook 使用与原理详解

wagmi Tempo 黑名单策略管理实战:policy.useModifyBlacklist Hook 使用与原理详解 【免费下载链接】wagmi Reactive primitives for Ethereum apps 项目地址: https://gitcode.com/GitHub_Trending/wa/wagmi 本篇技术指南以 wagmi 仓库中 Tempo 模块的 React…

2026/9/21 8:40:20 阅读更多 →
MiroFish:基于Docker的多智能体协同预测引擎

MiroFish:基于Docker的多智能体协同预测引擎

1. 项目概述:MiroFish不是鱼,是多智能体协同预测的“水下操作系统”MiroFish这个名字乍一听像某种深海生物或开源硬件项目,但实际它是一套面向复杂动态系统建模与实时决策支持的多智能体协同预测引擎(Multi-Agent AI Prediction E…

2026/9/21 7:39:00 阅读更多 →

最新新闻

可再生能源与电动汽车协同调度模型研究

可再生能源与电动汽车协同调度模型研究

1. 项目背景与核心价值去年参与某省级电网的智慧能源项目时,我第一次深刻体会到电动汽车充电负荷对配电网的冲击。当某小区同时有30辆电动车在晚高峰充电时,变压器温度报警触发了三次。这促使我开始关注如何利用可再生能源的波动特性来平抑充电负荷曲线&…

2026/9/21 20:19:24 阅读更多 →
天天好逼网性能优化从入门到精通:3步解决面试原理卡壳难题

天天好逼网性能优化从入门到精通:3步解决面试原理卡壳难题

天天好逼网性能优化从入门到精通:3步解决面试原理卡壳难题 面试被问“天天好逼网”底层并发模型,你脑子一片空白?别慌,这不仅是你的问题,也是无数开发者从入门到精通路上的坎。很多人盯着业务代码写,却忽略了性能瓶颈背后的原理,导致一到面试就露怯。…

2026/9/21 20:19:24 阅读更多 →
Pixel一键刷入KernelSU自动化工具实测:原理、踩坑与配置

Pixel一键刷入KernelSU自动化工具实测:原理、踩坑与配置

如果你手上的 Pixel 还在走“下工厂镜像 → 解包 payload.bin → 抠 boot.img → patcher 修补 → 再 fastboot 塞回去”这条老路,我强烈建议你停下来看完这篇。磨了一下午得到的结果,往往只是把一台设备从 A 版本升到 B 版本,下个 OTA 一来又…

2026/9/21 20:19:24 阅读更多 →
3个坑解决网站公司环境卡死,手写实现核心逻辑

3个坑解决网站公司环境卡死,手写实现核心逻辑

3个坑解决网站公司环境卡死,手写实现核心逻辑 配置环境就卡半天,依赖包冲突、版本不匹配、端口占用,这些问题在接手【网站公司】遗留项目时简直是家常便饭。很多新人对着报错日志抓耳挠腮,其实核心问题往往出在启动流程的隐性依赖上。与其反复重装…

2026/9/21 20:19:24 阅读更多 →
解决未能恢复iphone发生未知错误3194的完整示例

解决未能恢复iphone发生未知错误3194的完整示例

解决未能恢复iphone发生未知错误3194的完整示例 学会语法却不知怎么搭项目,是无数开发者从新手迈向工程师的坎。今天咱们不聊虚的,直接拆解【未能恢复iphone发生未知错误3194】这个让无数果粉抓狂的报错。这不是玄学,是底层机制在抗议…

2026/9/21 20:19:24 阅读更多 →
WSL2与云服务器部署Hermes Agent实战:从环境搭建到调试

WSL2与云服务器部署Hermes Agent实战:从环境搭建到调试

先交代一下背景。如果你最近在折腾本地大模型、自动化工作流,或者想把 Agent 类服务从笔记本迁到服务器上跑,那你大概率听过 Hermes Agent 这个名字。它本质上是一个能承载工具调用、多轮任务编排、模型调度的运行时框架,底层可以对接 OpenAI…

2026/9/21 20:18:24 阅读更多 →

日新闻

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程

agents-generator 决策矩阵全解析:从项目检测到 AGENTS.md 规则生成的 16 步判定流程 【免费下载链接】agentic-awesome-skills AAS Core is the local, agent-first control plane for complete catalog discovery, agent-owned selection, stack validation, and …

2026/9/21 0:00:01 阅读更多 →
gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析

gin-vue-admin 前端工具函数全景指南:src/utils 复用规范与源码级解析 【免费下载链接】gin-vue-admin 🚀ViteVue3Gin拥有AI辅助的基础开发平台,企业级业务AI开发解决方案,内置mcp辅助服务,内置skills管理,…

2026/9/21 0:00:01 阅读更多 →
Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

Wox 全功能插件开发实战指南:基于 Python / Node.js 宿主与 WebSocket 的持久化插件体系

桌面应用AI 应用插件系统 【免费下载链接】Wox A cross-platform launcher that simply works 项目地址: https://gitcode.com/gh_mirrors/wo/Wox 点击查看 免费下载 全功能插件(Full-featured Plugin)是 Wox 三类插件实现方式中能力最完整的…

2026/9/21 0:00:01 阅读更多 →

周新闻

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

Flutter for OpenHarmony游戏卡片渐变背景实战:从原理到性能优化

直接铺开项目本身吧。这几个月我一直在折腾一件事:用Flutter给OpenHarmony做一款游戏集合类的App,说白了就是把若干小游戏塞进一个壳里,用统一入口分发。这个方向本身不算新鲜,真正让我花了不少心思的,是首页那堆游戏卡…

2026/9/21 3:13:20 阅读更多 →
Word表格编号全攻略:从列表编号到题注交叉引用

Word表格编号全攻略:从列表编号到题注交叉引用

写Word文档,最让人头疼的往往是那些“看起来不起眼”的小问题。比如表格编号这事:今天在表后面多加了两个空白行,明天给客户交稿前发现整个章节的编号全部错位,光是挨个改序号就能耗掉大半个下午。我前阵子帮人整理一份上百页的技…

2026/9/21 2:19:36 阅读更多 →
从第一个站到第二个站:独立开发者的静态网站选型与落地实践

从第一个站到第二个站:独立开发者的静态网站选型与落地实践

1. 项目概述1.1 核心需求解析做独立开发者这几年,说实话,第一个网站上线的那天晚上我兴奋得没睡着。但等它跑了半年,流量惨淡、功能臃肿、代码自己都懒得看第二遍之后,我才慢慢琢磨明白一个道理:第一个网站是练手&…

2026/9/21 4:51:05 阅读更多 →

月新闻

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能

持续集成 流水线自动化与 声明式交付 实践:原型怎样变成可用功能分类:[AI/大模型]细分主题:AI 增强型 CI/CD 流水线自动化与 GitOps 实践:Agent 工作流、工具调用与任务拆解:从原型到生产的验收清单很多团队在尝试用大…

2026/9/21 15:36:51 阅读更多 →
容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场

容器编排 生产环境运维与排障实战:复盘记录怎样真正派上用场分类:[工程技术]细分主题:Kubernetes 生产环境运维与排障实战:可复制的项目复盘模板与决策记录大部分团队的事故复盘报告,最后都变成了躺在 Confluence 或钉…

2026/9/21 15:36:51 阅读更多 →
容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步

容器 容器化技术与镜像安全管理:核心链路应该先拆哪一步分类:[工程技术]细分主题:Docker 容器化技术与镜像安全管理:核心链路的逐步实现与关键代码取舍面对一个积累了五六年历史包袱的单体架构应用(包含 Web 接口、后台…

2026/9/19 23:35:34 阅读更多 →