两天20道算法题:双指针、动态规划与回溯的刷题复盘笔记 2026年刚开年那阵子我给自己定了个小目标把算法题的手感重新捡起来。说实话工作之后写业务代码的时间多了很多基础的数据结构和经典题型反而生疏了碰到稍微绕一点的题就总想去看题解这让我有点慌。尤其是看到不少人靠着系统刷题拿到了心仪的 offer或者在代码质量上明显更稳我就更觉得这件事值得认真做一次。所以 1 月 21 号到 22 号这两天我把自己关在书房里专门集中刷了两天题顺便把过程中的思路、踩坑、时间分配全部记了下来就是标题里这份“刷题笔记”。这篇笔记不适合打算三天速成的人它更适合那些跟我一样想稳扎稳打把算法基础补回来、并且真的愿意花时间做复盘的人。接下来我把这两天的完整过程、题型总结、调试教训和一些真实感受都写出来希望能给你一些参考。1. 刷题前的准备目标、范围与节奏1.1 为什么把目标定成“恢复手感”而不是“题海战术”这次刷题之前我认真想了一下自己的短板在哪里。如果只是漫无目的地每天刷几道题看起来努力实际上效率往往很低。因为刷题这件事最关键的不是“量”而是“题型识别”和“思路转换”。比如拿到一道题你能不能第一时间判断它属于双指针、动态规划、回溯还是图论这决定了后续的解题速度。所以我给这两天定的目标很简单不追求一天写几十道而是把高频的核心题型重新过一遍每道题都做透做完之后写出完整的思路复盘。具体操作上我把目标拆成了三层。第一层是基础数据结构数组、链表、栈、队列、哈希表这部分主要是恢复记忆力刷一些经典题巩固一下 API 使用第二层是高频算法思想二分查找、双指针、滑动窗口、递归、回溯、基础动态规划这部分是重头戏花的时间最多第三层是快速过一遍二叉树相关的问题因为树的题目面试出现频率实在太高了而且很多复杂问题都能化归到树的遍历上面。两天的时间我给自己定的量是 20 道题左右每一道都要求能独立完成并且整理出笔记。1.2 两天的题目范围与时间安排我按照自己的精力曲线把时间分成了几个小块。上午头脑清醒适合啃动态规划和回溯这种需要深度思考的题目下午稍微有点疲惫主要做双指针、哈希表这类套路比较固定的题晚上则用来做二叉树和整体复盘。这里我列一下我实际执行的时间表时间段刷题主题目标完成量第一天上午数组、哈希表、双指针6 道第一天下午滑动窗口、前缀和4 道第一天晚上二叉树遍历与递归4 道第二天上午动态规划入门背包、爬楼梯4 道第二天下午回溯算法组合、排列、子集4 道第二天晚上错题复盘与笔记整理整理全部记录这种安排最大的好处是每个时间段都有一个相对聚焦的主题大脑不需要频繁切换思维模式进入状态会更快。很多刷题新手容易犯的一个错误是“一天之内什么题都刷”比如刚做完一个链表题马上又跳到一个图论题结果每道题都刚开了个头就换了方向第二天回头一看全忘光了。我用的这种主题式刷法某种程度上相当于做了“批量练习”同一类型的题连续做几道以后套路感会非常明显进步也会更快。2. 高频题型的套路拆解与实操解析2.1 双指针与滑动窗口从暴力到 O(n) 的思维转变第一天上午我先刷了双指针。说实话双指针本身不是一个特定的数据结构它是一种优化思路。很多数组、字符串相关的题目暴力的做法是两层循环枚举复杂度 O(n^2)但当数据范围超过 10^4 的时候O(n^2) 往往就会超时。双指针的核心思想是利用数组的有序性或者某种单调性让两个指针分别从两端或者同向移动在一次遍历的过程中完成计算。我拿“三数之和”这道经典题举例。暴力做法需要三层循环枚举 i、j、k结果不仅要处理超时还得费劲去重。用双指针之后思路变成了先把数组排序然后固定一个数 nums[i]再用一左一右两个指针去查找剩下两个数。左指针从 i1 开始右指针从末尾开始如果三数之和大于 0说明右边的数太大了右指针左移如果小于 0说明左边的数太小了左指针右移等于 0 就记录答案。排序的复杂度是 O(n log n)双指针部分因为是固定一个点再扫一轮整体是 O(n^2)但比三层循环还是要稳定很多而且去重逻辑也更清晰。这道题我印象很深的地方在于去重。很多人写出来功能是对的但输出结果里有重复的三元组原因就是没有在找到一个答案之后主动跳过重复元素。这个细节只有在手写一遍、看到错误输出之后才能真正记住。滑动窗口和双指针的关系非常紧密我下午刷的“无重复字符的最长子串”就是典型代表。这道题维护一个窗口右指针不断向右扩展把新字符加入窗口左指针则根据情况收缩保证窗口内没有重复字符。这里的关键是你用哈希表记录每个字符最后出现的位置当遇到重复字符时直接把左指针跳到 max(左指针, 上次出现位置1)而不是老老实实一步一步挪。我在实际写代码的时候第一次漏掉了取最大值这一步结果在某些用例上左指针会往回退直接导致结果偏大。后来调试时打印出每一步的左右指针才发现了问题。这个细节其实就是滑动窗口题里最容易出错的地方。2.2 动态规划状态定义比转移方程更重要第二天的重头戏是动态规划。学 DP 的时候很多人第一反应是去背“背包九讲”里的转移方程但我自己的体会是如果你能清楚地回答“dp[i] 到底代表什么”这个问题后面代码几乎是水到渠成的事。我在刷题笔记里反复强调一句话“状态定义错了后面全是白做。” 这句话是怎么来的我拿最经典的“爬楼梯”举例假设要爬到第 n 阶每次可以走 1 步或者 2 步问有多少种不同的方法。如果不假思索很容易写出 dp[i] dp[i-1] dp[i-2] 这样的转移但如果问你 dp[i] 是“走到第 i 阶的方案总数”那么 dp[0] 该初始化为 1 还是 0 就会成为第一个坑。很多题解直接说 dp[0]1理由是“0 阶也算一种方案”但这对新手来说并不自然。换个定义方式dp[i] 表示“从第 0 阶走到第 i 阶的方案数”然后让 dp[0]1dp[1]1你会发现递推关系依然成立而且更好理解。这就是状态定义带来的连锁影响。动态规划里第二重要的东西是遍历顺序。还是拿我刷的“最小路径和”来说给定一个二维网格从左上角走到右下角每次只能向右或者向下走要让路径上的数字总和最小。这是一道非常经典的二维 DP 题。状态定义很简单dp[i][j] 表示走到网格 (i, j) 时的最小路径和转移方程就是从左边或者上边较小的那一个再加上当前格子的值。但这里有一个新手很容易搞错的地方初始化边界时第一行只能从左往右累加第一列只能从上往下累加不能混着来。我曾经为了图快直接在一个双重循环里处理所有格子结果第一行和第一列的值计算错了整个结果全盘崩掉。后来我的做法是先把边界单独初始化再处理内部格子这样思维负担小代码也更不容易出 bug。2.3 回溯算法把“撤销选择”想明白第二天下下午我集中刷的是回溯。回溯本质上是深度优先搜索的一种特例它的核心就是三个步骤做选择、递归、撤销选择。很多人在学习时会被“递归”劝退觉得太烧脑但我用了一个生活化的类比来帮助理解想象你要走出一个迷宫你每到一个岔路口就做一个标记选定一个方向往前走如果走不通你就按原路退回这个岔路口把标记擦掉再试另一条路。擦掉标记这个动作就是代码里的“撤销选择”。我刷“全排列”这道题时第一次写出来的代码只加了递归没有恢复状态结果输出的排列里很多元素重复出现。因为当你递归完一条路回到上一层时如果不撤销当前的“已使用”状态下一层循环就会认为这个元素已经被占用了。这个撤销动作代码上就三五行但逻辑上非常关键。我自己的经验是写回溯题的时候拿笔在纸上画出递归树把每一步的“选择”“递归”“撤销”分别标出来真的比闷头写代码要快得多。虽然动笔看起来很慢实际上很顶用。我这次刷题时几乎每道回溯题都会先画递归树思路直接清晰了一个档次。3. 刷题过程中的高频失误与排查实录3.1 边界条件一半以上的 bug 都出在这里两天刷下来我统计了一下自己犯的错发现一个扎心的事实真正因为“解法不会”而卡住的情况大概只占三成剩下的七成全部是边界条件处理出了问题。比如数组越界、空输入、只有一个元素、整数溢出、字符串为空这些情况不专门去测的话代码很容易在“看起来没什么问题”的状态下挂掉。我之前写“二分查找”就翻过车默写下来很简单但真正在 LeetCode 上跑的时候死循环了。后来定位了半天发现是 while 条件是 left right 还是 left right以及 mid 更新的时候要不要加一的问题。这两个问题不搞清楚二分查找就是“玄学”。我整理出一个规律供大家参考循环条件mid 更新方式适用场景left rightright mid - 1left mid 1标准区间查找直接返回索引left rightright midleft mid 1寻找左边界left rightright mid - 1left mid寻找右边界mid 需向上取整这个表很重要因为我发现很多边界问题不是能力问题纯粹是“没有系统整理过”。比如你用的是左闭右闭区间那 left right 是对的选择否则空区间时循环直接退出根本不会进入判断。但如果你写的是左闭右开区间那循环条件又不一样了。写二分查找之前先想清楚“我维护的这个区间到底长什么样”再动笔这样基本上就能避开很多坑。我把这段总结直接写进了笔记里标注为“反复要看的重点”。3.2 复杂度误判一场隐藏的面试雷区除了边界条件还有一个容易被忽视的问题是对时间复杂度的误判。我在刷“最长回文子串”这道题时最开始想到的是暴力枚举所有子串再逐个判断是否是回文逻辑倒是简单但总复杂度是 O(n^3)数据范围稍微大一点就直接超时。后来我改成中心扩展法枚举每个中心位置然后向两边扩散复杂度直接降到 O(n^2)。这道题让我意识到刷题的时候不能只满足于“做出来”还要逼自己想一想“如果数据范围扩大十倍我的代码还能跑吗”我自己的做法是在每道题的笔记里都额外写一行“时间复杂度 空间复杂度”并且在提交通过之后刻意看一眼最优解的复杂度是多少然后思考差距在哪里。像“最长回文子串”这种题如果只看暴力解你永远学不到 O(n) 的马拉车算法但你如果已经掌握了 O(n^2) 的中心扩展法再去看马拉车理解起来就轻松不少。这个学习路径比一上来就背马拉车要扎实得多。这也是为什么我特别建议刷题时不要一次性追求最优解而是先有暴力解做保底再一步步优化这个“渐进优化”的过程本身就是宝贵的能力。3.3 工具与环境的折腾本地调试到底值不值得刷题过程中还有一个很多人纠结的问题到底是在网页编辑器里直接写还是拉到本地 IDE 里配合测试用例调试我个人这两天的体验是本地调试非常值得但要想清楚成本。我用的方法是在本地建一个简单的测试脚本把题目里的示例用例写进去然后配合断点和 print 逐步观察变量变化尤其是双指针、回溯这类需要观察“状态什么时候被修改了”的题目这种方式能帮你秒级定位问题。当然本地调试也有坑。我之前在本地写得好好的代码一粘到在线评测系统就报编译错误后来发现是本地没有严格按照题目的输入输出格式来处理比如没有处理多组输入、没有 trim 掉多余的空格等。所以我现在养成一个习惯代码通过本地测试以后还会专门检查一下输入输出的格式再提交到评测系统里。这个习惯让我避免了很多“毫无意义的失败提交”。4. 刷题效率工具体验与笔记方法分享4.1 我的错题本分层记录法这次刷题我做了一个很有效的改变分层记录错题而不是把所有东西混在一个文档里。我分成了三层第一层是“思路错误”这类错题意味着我完全没方向需要在笔记里重新写清楚这类题型的套路与思考路径第二层是“边界条件错误”这类题我解法大方向是对的但在特殊用例上出了问题因此我会在笔记里用红字专门标注边界值比如空数组、负数、最大整数等第三层是“代码实现错误”比如变量名写错、索引不对、忘了修改循环变量这种我会随手写一句“低级失误下次注意”而不需要花太多时间。这个分层记录法帮我节省了很多复习时间。因为第一层错题才是真正值得反复看的第二层只需要考前扫一眼第三层干脆可以忽略。刷题到最后最忌讳的其实不是错得太多而是错得太多但从不分类结果复习的时候被海量信息淹没什么都想抓什么都抓不住。4.2 笔记里的“一句话总结”有多重要我在每一道题的笔记末尾都会逼自己写一句“一句话总结”。比如“三数之和排序 双指针注意跳过重复元素”“爬楼梯斐波那契状态压缩空间优化到 O(1)”。别小看这一句话它其实是在强迫我用最精炼的话把这道题的核心讲清楚。如果一句话说不清说明我对这道题的理解还不够深那就得再回去看看。而且这种一句话总结等到后面复习的时候特别有用。两天结束之后我翻看自己写下的这二十多句话基本就能在十几分钟内把所有题目的套路在脑子里过一遍相当于做了一个快速索引。如果当时没写这句话那我复习时重新看每道题就得从完整笔记里重新读一遍效率会低很多。5. 实操模板与通用解法清单5.1 经典题型的“最小可用代码”模板刷完这两天我最大的收获之一是整理出了一批“最小可用代码”模板。所谓最小可用就是删掉所有花哨的写法只保留最核心的逻辑结构方便在面试紧张的时候快速回忆起来。这里我挑几个分享出来二分查找模版左闭右闭def binary_search(nums, target): left, right 0, len(nums) - 1 while left right: mid (left right) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -1回溯模版def backtrack(path, choices, result): if 满足结束条件: result.append(path[:]) return for choice in choices: 做选择 backtrack(path, choices, result) 撤销选择滑动窗口模版left 0 for right in range(len(s)): 更新窗口状态 while 窗口不满足条件: 收缩左边界 left 1 更新结果这几个模板我写在了笔记最前面作为一个快速参考区。面试前我通常会把这些模板快速过一遍让自己进入状态。要注意的是模板是帮你在紧张时有东西可以写但不代表你不需要理解背后的原理。有几次我只是机械地套模板结果题目稍微变形就写了很久后来才意识到模板必须配着原理解释一起记忆才真正有用。5.2 两天 20 道题的完整清单与复盘这里把我实际完成的部分题目列表列出来给粉丝们一个参照。当然题目数量不是核心关键是覆盖面分类题目核心考点我的错误/收获哈希表两数之和空间换时间字典存储想到了哈希表但忘记处理重复元素双指针三数之和排序 双指针 去重去重逻辑不熟练第一次漏掉滑动窗口无重复字符的最长子串窗口左边界更新忘记取 max左指针回退二分查找搜索旋转排序数组二分变体区间判断条件写反调试很久动态规划爬楼梯斐波那契状态压缩老题但要考虑 dp[0] 的定义动态规划最小路径和二维 DP边界初始化第一行第一列处理错误回溯全排列撤销选择漏掉撤销结果重复回溯子集选或不选两种视角迭代法思路更清晰二叉树二叉树的中序遍历递归与迭代栈迭代写法忘记维护 left 链二叉树二叉树的最大深度后序遍历递归思路清晰一行代码解决复盘完这张表我最大的感受就是我犯的错真的很典型而且大多集中在“对状态变化的理解不够深”上。比如滑动窗口左指针回退、回溯漏撤销、DP 边界初始化错误归根结底都是没有想清楚“每一步操作之后状态应该是什么”。所以后面的刷题复习里我也调整了策略每道题写完之后多花三十秒在脑子里把“状态变化”过一遍而不是急着去做下一道题。这种方法比单纯追求“今天刷了十道题”的成就感要有效得多。6. 两天刷题后的直接经验与建议6.1 给不同基础的人两条刷题路线建议刷题这件事其实很讲究“匹配度”。我之前也走过弯路刚开始的时候一上来就刷 hard 题结果被虐得体无完肤自信心严重受挫。按照这两天的体验我给不同基础的朋友两条路线参考。如果你基础比较薄弱连树的遍历都还有点陌生我建议先从“数据结构基础题”入手比如数组删除元素、链表反转、用栈实现队列这一阶段目标不是会做难题而是熟悉语言 API、掌握常见数据结构的基本操作至少刷三十道左右再进入下一阶段。如果你已经能独立做出简单题只是中难题容易卡住那我的建议是主攻“高频算法专题”就是我今天写的双指针、滑动窗口、动态规划、回溯这几个方向每个专题至少连续刷十道再交叉混合训练强迫自己识别题型。6.2 时间管理与心态调整的碎碎念两天高强度的刷题说实话挺累的。尤其是第二天下午写回溯的时候我的脑子明显感觉转不动了看递归调用栈看了半天都没绕出来。后来我站起来休息了十五分钟洗了把脸再回来重新画递归树才成功把思路理清。这个过程让我意识到刷题本质上也是一个体力活集中刷题时一定要给自己留出足够的休息时间不要硬撑。我还发现一个小技巧把手机放到另一个房间只留一台电脑和一本书在书桌上减少外界干扰这样进入心流状态的速度会明显变快。这种专注状态一旦建立刷题效率能翻倍而且对思路连续性的帮助特别大。那两天里我还有一个心态上的变化接受“不会做”是常态而不是耻辱。很多人在刷题时遇到不会的题第一反应是烦躁然后马上去看题解。我的做法是先自己硬想十五分钟如果十五分钟后还是毫无头绪再去看题解。但看完题解之后不是直接复制粘贴而是把题解关上用自己的话从头写一遍代码哪怕写出来的和题解不完全一样也没关系。这个过程很残忍但见效极快。刷完两天题我能明显感觉到自己面对陌生题目时的第一反应已经从“好难”变成了“先分析一下题型和可能的解法”这种思维方式的转变可能比多会做几道题本身更重要。我个人在实际操作中的体会是刷题笔记这类东西最重要的不是记了多少而是回头看多少。很多人的笔记写完就吃灰那就失去了意义。我这两天的笔记虽然只是简单的 Markdown 文档但我在里面加了大量的复盘和标注目的就是让一周后的自己还能快速看懂当时卡壳的地方。如果你也想尝试集中刷题我真心建议你也建一个自己的错题分层体系哪怕只是拿个记事本也比什么都记不住要强得多。希望这份刷题笔记能给你一点启发让你找到适合自己的节奏。