复试机试是一场“手熟”的比赛先不谈会不会做思路转不转得动、代码能不能一次过往往取决于你每天有没有动键盘。我给自己定了一个相对保守但可持续的节奏每天固定3题跑完OJ、更新博客、小结复盘。复盘对象是第4天的字符串/链表、第5天的二叉树遍历与性质、第6天的动态规划和BFS。这三天正好覆盖了机试里最常被当作“送分题”的几类考点——挡住大多数人的不是算法本身而是说不清原理、写不对边界、没养成调试节奏。这篇文章就把这三天的9道题、常见坑点以及我用来压节奏的方法全部摊开给正在准备复试的人一个可以照着做的参考。1. 打卡机制与选题逻辑先跑起来再谈强度打卡这件事最怕两个极端一种是一天宕十几题觉得自己很强三天后断档另一种是反复刷自己会的题刷得很舒服结果新题型全没碰过。我的做法是给自己设一个最低预期每天3题工作日晚上雷打不动90分钟周末补到120分钟。跑完就写复盘不管觉得自己做得多烂都要写跑不通的题当天不硬耗记录思路卡点之后明天再碰。1.1 为什么是“每日3题”而不是“一次性10题”复试准备周期一般是两三个月这段时间除了OJ还要穿插笔试科目、英语口语、机试环境熟悉、面试项目复盘时间真的不够用。按每天3题来算两个月就是180题左右覆盖各大OJ的高频题、常考模板和新题已经足够。更关键的是每天3题能让你保持“手感连续”而不是“体力透支”。算法题的本质是模式识别加肌肉记忆。模式识别需要你见足够多的题而肌肉记忆需要重复。一次性做10题第二天可能什么都记不住一天只做3题做完当晚复盘一下第二天再动手写这几题的核心模板你会发现一个星期后看到类似题手指已经先于大脑反应了。这个节奏还有另一个好处方便安排难度梯度。我一般会把每天的3题拆成“回顾1题 中档1题 进阶1题”的组合。回顾题是前两天做过的同类题重新写一遍只写核心函数不写完整流程中档题是主流面试和高校复试机试里最常见的类型比如区间贪心、链表操作、树的遍历进阶题则是稍微带点弯的比如动态规划、DFS剪枝、双端BFS这类做不出来不丢人第二天再想一遍。注意每天3题不是每天只做3题而是保证最少3题。如果某天状态特别差下限就是3题状态好可以加到5题但不要加到停不下来否则第二天容易产生“已经刷够了”的错觉。1.2 题型配比与时间盒设置我不太建议连续几天只刷同一类题。复试机试考的是综合能力比如常见高校的OJ题库往往第一题考简单模拟第二题考基本数据结构第三题考搜索或贪心第四题才上动态规划。连续刷单一类型会让你在混合型考试里反应变慢。我给自己定的三天循环是这样的时间题1回顾题2中档题3进阶总时间第4天字符串全排列回溯链表环检测区间合并90分钟第5天二叉树镜像二叉树层序遍历BST合法性验证90分钟第6天0/1背包模板最长递增子序列迷宫最短路径BFS100分钟这个表格看上去很简单但背后有一个很重要的原则每天至少完整写一遍代码并提交通过才算真的做过。不能看题五分钟然后翻题解那样没用。我自己有一个严格的纪律一道题开始实现前先在纸上或者注释里写出三要素——输入范围、边界情况、大致复杂度写完核心逻辑后先用样例跑再用自己构造的边界用例跑最后才提交。这里特别说一下时间盒也就是给每道题设置最长的思考时间。我的设置是回顾题20分钟中档题30分钟进阶题40分钟。超过时间直接看题解但要记录自己卡在哪一步是想不到用某个数据结构还是递归出口不会写还是转移方程推不出来。等到写复盘的时候把卡点单独列出来第二天回顾时先不看题解把核心代码重新默写一遍。2. 第4~6天题目复盘九个考点的归纳与延伸九道题说多不多说少不少但它们分别覆盖了字符串/DFS、链表/双指针、排序区间、二叉树递归、二叉搜索树、动态规划入门、BFS图搜索等机试核心。下面按天拆开讲每一题都会说清楚当时的思路、实现要点、以及在复试现场需要表达的“口述版本”。2.1 第4天字符串与链表的高频阵地这三道题是我第四天打卡的选择看起来基础但实战里出错率极高。题4-1 字符全排列DFS回溯这个题要输出一个字符串所有字符的全排列字符串不含重复字符但要求按字典序输出。一开始我直接套了回溯模板void dfs(string s, int index) { if (index s.size()) { ans.push_back(s); return; } for (int i index; i s.size(); i) { swap(s[index], s[i]); dfs(s, index 1); ans.push_back(s); // 这里就错了 swap(s[index], s[i]); } }写了一半就发现问题字典序要求下直接交换然后再回溯会产生重复排列和乱序输出。正确的做法是先把字符排序然后用一个布尔数组标记当前层的使用状态每一层从0开始遍历跳过已经用过的字符这样才能保证剩下的字母按顺序进入下一层输出的就是字典序。这个题在复试现场的口述版本应该是先用sort排序然后dfs(pos)在循环里如果当前字符和上一个相同且上一个没被用过就跳过这是去重。swap回溯适合求排列数量不适合字典序输出。题4-2 链表环检测快慢指针给一个单链表头节点判断是否存在环。这个题的白板写法特别短ListNode* slow head, *fast head; while (fast fast-next) { slow slow-next; fast fast-next-next; if (slow fast) return true; } return false;但恰恰因为太短很多人忽略了两个细节第一是fast-next判空不然fast-next-next会空指针异常第二是空链表和单节点链表应该直接返回 false。调试的时候我还发现一个很容易被忽略的点快慢指针相遇不一定是环入口要找入口还得在相遇后从头再走一遍用数学归纳可以证明它们会在入口相遇。复试如果要求口述这两层能分开讲考官基本知道你是真懂。题4-3 区间合并排序线性扫描给一组区间要求合并重叠区间。我第一次写的时候只想到了挨个比较相邻区间但没有先排序导致合并顺序乱七八糟。正确做法是先按区间起点排序然后维护一个当前合并区间的右端点vectorvectorint merge(vectorvectorint intervals) { if (intervals.empty()) return {}; sort(intervals.begin(), intervals.end()); vectorvectorint res; res.push_back(intervals[0]); for (int i 1; i intervals.size(); i) { if (intervals[i][0] res.back()[1]) res.back()[1] max(res.back()[1], intervals[i][1]); else res.push_back(intervals[i]); } return res; }这个题真正的坑在于“重叠”的定义a[1] b[0]就算重叠不管是否包含。还有一个面试里经常追问的问题是排序的稳定性其实区间合并不需要稳定排序因为只依赖起点大小。2.2 第5天二叉树的递归与迭代切换第五天这三题都是树的范畴但考察方向完全不同递归、队列、中序遍历三种技术各来一遍。题5-1 二叉树镜像镜像的意思是把每个节点的左右子树互换。递归写法很干净TreeNode* mirrorTree(TreeNode* root) { if (!root) return nullptr; swap(root-left, root-right); mirrorTree(root-left); mirrorTree(root-right); return root; }这个题容易在注入递归时把顺序搞错。其实先交换再递归左右子树是可以的先递归后交换也可以。害怕递归走错方向的话可以自己画一棵三层的完全二叉树走一遍调用栈这个方法比背模板管用得多。题5-2 二叉树层序遍历BFS队列层序遍历的模板我写了不下三十遍但第五天打卡时依然栽在一个概念上输出格式要求每一层一个数组而不是一行全平铺。区别就在循环里要先获取当前队列长度int levelSize q.size(); for (int i 0; i levelSize; i) { TreeNode* node q.front(); q.pop(); level.push_back(node-val); if (node-left) q.push(node-left); if (node-right) q.push(node-right); }这里必须先把levelSize存下来因为队列元素在循环中会变化如果写成for (int i 0; i q.size(); i)每次循环都会重新计算队列长度结果就是同一层的节点没处理完下一层的节点也被顺手带进去了。这个错的隐蔽性很高OJ不会报编译错误只会输出错乱。题5-3 验证二叉搜索树BST合法性这个题让我印象最深因为6天复盘时它依然是我默写练习的候选。判断BST不能只看左右子节点和根的大小关系必须保证整个左子树都小于根整个右子树都大于根。规范写法是递归传上下界bool isValidBST(TreeNode* root, long long lower, long long upper) { if (!root) return true; if (root-val lower || root-val upper) return false; return isValidBST(root-left, lower, root-val) isValidBST(root-right, root-val, upper); }这里我用long long而不是int因为部分测试用例会用INT_MIN和INT_MAX作为边界你再用int传参就直接冲突了。这是很典型的“看着对、测着挂”的边界问题预判的方式是看数据范围如果节点值范围是[-2^31, 2^31-1]那上下界就得能容纳这两个数以外的数一般用long long或者干脆传指针。2.3 第6天动态规划与搜索的“送分题”不送分第六天的三题难度直线上升。不是算法的原理难而是很多人总栽在初始化上导致合不上板。题6-1 0/1背包模板背包问题几乎每个复试OJ都考过某种变体最基础的是N件物品每件重量 weight[i]、价值 value[i]给定背包容量 C求最大价值。int dp[10005]; memset(dp, 0, sizeof(dp)); for (int i 1; i N; i) { for (int j C; j weight[i]; j--) { dp[j] max(dp[j], dp[j - weight[i]] value[i]); } }这个题的标准讲法是“先遍历物品再逆序遍历容量”目的是让每个物品只能选一次。如果你正向遍历容量就会出现同一个物品被重复拿的情况这是0/1背包和完全背包的编码分水岭。复盘时我特意把一维和二位都写了一遍确保自己不依赖模板能够从状态定义推导出递推公式。提示遇到“变种题”先确认商品能不能重复取能重复取就正向循环不能重复取就反向循环模版决策比状态转移更常出错。题6-2 最长递增子序列LISO(n²) 的DP做法非常直观dp[i]表示以第i个元素结尾的最长递增子序列长度。但是复试时间有限高频考法是要求 O(n log n) 的二分优化版本vectorint tails; for (int x : nums) { auto it lower_bound(tails.begin(), tails.end(), x); if (it tails.end()) tails.push_back(x); else *it x; } return tails.size();可能有人第一次看会质疑tails数组里存的不是真正的LIS序列为什么它的长度就是LIS长度这里的核心是贪心思想我们维护每个长度下可能的最小末尾值这个值越小后续拼接的空间越大。这个题在复试口试时一直被追问证明过程所以复盘时我把“为什么 tails 替换不会影响最终长度”的逻辑理顺了才能讲得清楚。题6-3 迷宫最短路径BFS给一个m * n的0/1矩阵0表示可通过1表示障碍求从起点到终点的最短步数。BFS 模板本身不难queuepairint,int q; int dx[4] {1, -1, 0, 0}; int dy[4] {0, 0, 1, -1}; vectorvectorint dist(m, vectorint(n, -1)); dist[sx][sy] 0; q.push({sx, sy}); while (!q.empty()) { auto [x, y] q.front(); q.pop(); for (int i 0; i 4; i) { int nx x dx[i], ny y dy[i]; if (nx 0 || nx m || ny 0 || ny n) continue; if (dist[nx][ny] ! -1 || grid[nx][ny] 1) continue; dist[nx][ny] dist[x][y] 1; q.push({nx, ny}); } } return dist[tx][ty];当时我的问题出现在“先标记再入队”还是“出队时标记”的犹豫上。正确做法是入队时立刻标记dist否则重复入队会让队列爆炸。另外我习惯把所有距离初始化为-1这样既当访问标记又当步数数组一箭双雕省掉额外的visited。迷宫题在复试现场还很适合口头考察“如何求最短路径长度”和“如何恢复路径”后者通常再开一个prev数组就能完成。3. 复盘过程中最值得拿出来说的调试与边界问题九道题全部提交通过之后我做的第一件事不是庆祝而是把每道题的提交错误信息翻出来逐条归纳。所谓复盘重点不在“我做出几道”而在“我错在哪里、下次怎么防”。3.1 三个真实翻车案例第一个翻车是字符全排序列举题。开头我用了交换回溯结果字典序对不上当时我还在纠结是不是排序不够细后来才发现字典序不是靠最后整体sort的而是在DFS每一层就保证剩余字符按序排列。解决办法是把同层去重条件补上笔试里如果没加这个条件结果就是大量重复排列。第二个翻车在BST验证我第一次提交用了int边界直接爆了。当时题目给的两个测试点分别是空树和单节点我按直觉写了两个 if 判断结果单节点直接返回 true 没问题空树也返回 true 没问题可一跑完整测试就有两个样例超时。不是因为时间复杂度高而是int边界传递导致判断误判节点值取到了边界值时上下界条件被意外触发。改long long后一次过这个经验后来帮我避了好几次类似的坑。第三个翻车是0/1背包的空间优化。我想省数组从一位数组开始写结果把容量循环写成了正向提交后样例结果偏大很明显是同一个物品重复拿了。当时我没有马上改方向而是先手动模拟一组数据一件物品重量3价值5背包容量5。正向循环完dp[5]变成了10而不是5这个模拟比翻题解说服自己多了。3.2 把边界检查变成习惯动作我给自己做了一张“边界检查清单”每次提交前扫一眼不用太费脑但能挡住大部分低级错误空输入空数组、空字符串、空链表、空树全部得单独确认单元素输入链表单节点、字符串单字符、矩阵 1x1往往最容易暴露索引问题值域极值节点值、数组元素是否接近INT_MIN/INT_MAX涉及比较和减法时注意溢出循环边界二分查找左闭右闭还是左闭右开遍历时是否取了size()导致越界图搜索访问标记入队时标记还是出队时标记决定了会不会死循环这张清单我在第4~6天每天调用三次到第六天基本能闭眼背出来。它还有一个额外效果帮我养成了“写代码前花30秒看数据范围”的习惯。OJ题基本会给出n的范围这直接决定了应该用 O(n²) 还是 O(n log n)用 DFS 还是 BFS要不要开 long long。3.3 复盘笔记怎么写才有用我这里说的复盘笔记不是把题目和题解复制粘贴而是写给自己看的“路径图”。我每道题留三行第一行写题号和一句话概述考点比如“区间合并排序维护右端点”第二行写卡点比如“没想到先排序直接用原始顺序硬合并了”第三行写下次提防什么比如“下次先看数据范围再定复杂度重叠条件要覆盖取等”日积月累这部分其实就是面试前最靠谱的复习资料。到第4~6天的时候我已经攒了十几条这样的笔记回看时能看到自己从“不会写回溯去重”到“主动判断同层是否需要剪枝”的进步这种进度感是持续打卡的重要动力。4. 每日3题的自我管理如何在复试战线里保证可持续这部分看起来和算法无关但第4~6天能不能坚持拼的其实不是智力而是管理。刷题最怕的不是题难是断了之后补不回来一断就全盘崩塌。4.1 把每日题量装进固定时间盒我每天给自己预留的 OJ 时间分成两段。第一段晚饭后 19:30–20:00这30分钟只做“回顾题”也就是前一天的三道题里挑一道重做不做新的目标是在不看题解的情况下15分钟内通过。第二段 20:00–21:00 或延长到 21:30做中档和进阶题。设置固定时间盒的好处是你不用每天思考“今天到底要不要刷题”到点打开电脑就行决策成本几乎为零。4.2 放弃完美主义允许自己只解一道题总有些日子状态差比如刚看完几个小时笔试科目脑子确实转不动。这种情况下我不会逼自己一定完成3题而是允许自己只完整写一道题另两题只做思路复述不写代码。复述思路也要打开IDE把注释写下来这样可以确认自己知道要用什么数据结构、大体几重循环。这套“最低完成标准”保证我没有一天因为心理负担而中断中断是打卡系统里最需要避免的事件。4.3 把博客复盘当成二次学习写博客复盘不一定是为了给别人看更是逼自己把糊在一起的知识梳理成语言。每天3题做完了我会花30分钟写复盘内容包含今天三道题分别考什么属于哪个知识板块我卡在哪一步为什么卡卡了几分钟哪些边界条件第一次没想到今天有没有一个可以推广的解题套路比如第5天复盘BST我总结出一个通用模式“子树合法性验证 递归传上下界”第6天复盘背包我总结出“背包九讲的第一讲本质在于倒序容量”。这些概括虽然在很多博客里能看到但自己写一遍和看别人写一遍记忆牢固程度完全不一样。4.4 把OJ能力转化为复试面试表达每天做OJ除了要过机试复试面试也可能让手撕代码。有两天我发现一个现象自己能把代码写对但让口头讲清楚为什么用快慢指针、为什么用BFS反而有点前言不搭后语。从第5天开始我要求自己每道题提交通过后用一分钟对着屏幕复述一遍思路要求达到“不用看代码也能讲出核心逻辑和复杂度”。比如链表环检测我的口述模板是用两个指针一个每次走一步一个每次走两步如果有环快指针一定会在某一圈追上慢指针如果无环快指针会先到达空节点循环自然结束。这样讲完考官基本能判断你理解了问题本质而不是背了个模板。这个方法在复试现场远比刷题数量值钱。5. 常见问题速查与心态调节实录复盘第4~6天的过程中我也在持续观察自己做错题的规律。下面这些问题不是随口说的是真实发生过并且值得围观的人。5.1 为什么连续几道题都卡在同一种边界错法比如 BST 验证我用int边界挂了两次LIS 二分我一开始把upper_bound和lower_bound用混了找非递减子序列和严格递增子序列用的查询位置完全不一样。这类问题本质上都是“了解模板但不了解边界含义”导致的。复盘的解决办法是给每个模板配一个“为什么这样取边界”的小结而不是只背代码。5.2 一道题超过40分钟还做不出来怎么办我给自己定了一条规则超过40分钟直接看题解但看完题解之后必须立刻关上题解自己默写一遍。默写不出来的部分就是真正没有理解的部分第二天回顾时必须先做这道题。这个规则能避免两个后果一是浪费时间在死磕上产出极低二是产生“看懂了就是会了”的错觉实际动手就穿帮。5.3 中期疲劳和厌烦期怎么破做到第4~6天左右最容易出现的心理状态是“明明刷了题却感觉自己什么都不会”这是典型的平台期。我的应对策略是切换成“难度混合模式”把当天进阶题换成一道自己已经掌握的简单题用一次酣畅淋漓的AC来对冲挫败感。同时把打卡目标从“学会新题”暂时降级为“保住手感和心情”因为坚持这件事本身的意义有时候比某一题的解出更大。5.4 已经AC过的题过几天又不会写了怎么办这是最正常的现象完全不需要恐慌。我自己的经验是人对代码的记忆周期就是3~7天AC一次只代表当时会了。真题复现率高的题目必须放入“三刷名单”第一次写完不会第二次隔一天默写第三次隔三天默写。每天3题里的“回顾题”就是为这个机制服务的它看起来占用了新题时间实际上是在巩固重复出现的考点整体收益更高。5.5 关于时间与效率的几条心得把9道题摊开复盘后我的体感是真正解决这些问题的核心不在于多刷而在于高频、短周期、结构化反馈。每一个卡点如果当时不记录第二天就忘了每次边界错误如果不上升到检查清单下一个类似题还会再犯。这套方法论同样适用于笔试中的机试模拟、英语口语准备和项目复盘本质都是“输入 - 练习 - 反馈 - 修正”的四步循环。回到打卡本身我个人最满意的一点是我通过这三天把原本“今天又得刷题”的被动感慢慢转成了“今天记一下这题还有没有别的写法”的主动好奇。临近第6天时我做题前会先想三件事这道题数据的规模暗示什么复杂度、边界条件会不会让我挂掉、提交通过后我能不能在一分钟内讲清楚。米尺的准确度正是靠每天前几十道木板的刻度校准的机试水平也不是一次模拟赛能拉满的每一天的3题才是那个慢慢捏合自信和时间的最短积分。这9道题只是整个复试准备的很小横截面但它们构成了我后续所有练习节奏的一块重要基石。如果你也在准备复试OJ愿这个“日拱一卒”的错法能帮你少一点自我怀疑多一点对下一题的确定性。
阅读完成 · 觉得有帮助?