ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

第十五周刷题总结:从能做出来到讲得清楚

第十五周刷题总结:从能做出来到讲得清楚 第十五周刷题总结从“能做出来”到“讲得清楚”刷题这件事第十五周是个很有意思的节点。前面三个月的新鲜感早就褪了肌肉记忆开始形成但瓶颈感也在同一时间冒出来题量上去了看到题却总觉得“见过但不会做”。我自己的第十五周刷题总结主线不再纯粹是“又刷了多少道”而是从题型分类、套路沉淀、复述检验三个维度做了一轮系统性的复盘。这篇文章就是这周复盘的全记录包含了我对选题思路、典型套路、踩坑过程的具体拆解适合正在按周推进刷题计划的人也适合准备面试算法题时总感觉差一口气的人。这周我一共刷了42道题其中新题28道二刷及以上14道集中在数组、二叉树、动态规划三个板块外加每天两道热题保持手感。数据不算夸张但重新组织和输出的内容比之前四周都要扎实。因为到第十五周我开始强制自己把每道题不只是“跑通”而是能用一两句话讲清楚“为什么这么解”。这个转变带来的效果非常明显以前很多一知半解的题现在能自己推出来以前每次都要翻答案的模板题这周开始能够独立默写核心代码。1. 第十五周刷题概况与选题思路1.1 本周核心范围与数据回顾按照老规矩每周的第一天先定范围。第十五周我没有继续开新专题而是做了两件事第一把前几周刷过的区间问题、二叉树遍历、基础动态规划做了一次横向整理第二每个分类挑出高频题和易错题重新做一遍并补充了完整注释。本周记录如下新题数量28道其中数组类8道、二叉树类9道、动态规划类7道、其他栈、哈希、双指针4道二刷及复现14道全部要求“不看答案独立完成并输出题解思路”周中错题复刷率100%每次错题都记录到了错题本并在三天后重新写一遍核心代码累计耗时约21小时平均每天3小时周末会额外加一场两小时的完整模拟这个数据放在刷题大军里根本不算多但第十五周我更在意的是“吃透率”。所谓吃透我给自己定的标准是过两周再看这道题不假装熟悉能直接说出考点、边界条件和最优解推导路径。这个标准定下来之后刷题节奏明显慢了下来但踏实了很多。1.2 周计划安排与选题策略很多人在周计划阶段容易犯一个错误按题目难度排期看到中等题就跳看到困难题就发怵结果一周下来全是简单题或者全是同一个类型的题。我把第十五周的安排分成三个部分周一至周三集中处理“旧题复盘 同类新题扩展”比如二叉树部分先看前两周做过的前序、中序、后序遍历再延伸到层次遍历、之字形遍历、最近公共祖先。周四至周五主攻动态规划的“状态定义训练”不刻意追求难题而是把爬楼梯、打家劫舍、最长递增子序列、不同路径这几类常见模型反复对比搞清楚为什么有的题一维dp就够了有的必须二维。周末做两场限时练习一场用来模拟真实笔试环境一场用来输出题解写给别人听。这个策略的核心逻辑是每周一个主题围绕主题找变体而不是每天换一个完全不同的专题。从第十五周的效果看对比之前碎片化刷题正确率提升了大概20%左右尤其是看到题目后的第一反应明显从“这是什么题”变成了“这题属于哪个模型的变体”。2. 本周吃透率最高的三类题型套路2.1 区间类问题从暴力解法到差分数组的思考链这周数组部分我重新梳理了区间问题。区间问题在面试里出现频率极高但真正难的不是判断边界而是选择数据结构。以经典的“会议室问题”给定一系列会议时间区间判断能否参加所有会议为例。最暴力的做法是两两比较区间是否有重叠时间复杂度O(n^2)。但稍微观察一下就会发现如果先把所有区间按开始时间排序再检查相邻两个区间是否有重叠一次遍历就够了排序O(n log n)检查O(n)总复杂度O(n log n)。这就是区间类问题第一个核心思想排序后用相邻关系替代两两比较。接着我把它引申到“合并区间”“插入区间”“会议室II需要多少间会议室”这三道变体题。合并区间需要在排序后维护当前覆盖范围能合并就合并不能合并就输出上一段会议室II的常见思路则要换一个角度把开始时间和结束时间分别排序然后模拟指针推进。第十五周我在这个分类里学到了一个以前没仔细用的工具——差分数组。对于“给定多个区间计算每个点的最大覆盖次数”这类问题差分数组可以直接把区间更新的时间复杂度从O(k)降到O(1)这个思路在很多题里都能迁移。日常理解的话把差分数组想成“一个本子每个区间的开始位置记一个1结束位置后一个位置记一个-1”最后从前到后累加就能知道每个点被多少个区间覆盖。这个方法在“航班预订统计”里几乎是标准解法。2.2 二叉树遍历模板一个核心框架吃透迭代与递归二叉树题在第十五周的复盘中占了最大比重因为我发现自己过去对遍历的掌握还停留在“递归套模板”的层面一旦面试官要求写迭代写法就容易卡壳。先说递归。二叉树前序、中序、后序遍历的代码只有几行本质上都是在访问当前节点这件事上做文章void dfs(TreeNode* root) { if (!root) return; // 前序位置 dfs(root-left); // 中序位置 dfs(root-right); // 后序位置 }关键是理解这三个位置到底意味着什么前序位置是在进入节点时立刻处理中序位置是在处理完左子树之后、右子树之前后序位置是在左右子树都处理完返回时。很多树的题目思考“应该在哪个位置写处理逻辑”就能迎刃而解。但递归的问题在于一旦树很深或者面试官追问栈溢出和迭代实现就是绕不开的点。这周我把迭代写法彻底练了一遍。核心是用一个显式的栈模拟递归过程。前序遍历最简单每弹出一个节点就处理然后先压右孩子再压左孩子中序遍历需要一个指针变量先一路向左压栈再逐个弹出处理并转向右子树后序遍历则可以用“前序遍历的镜像”来做先压左孩子再压右孩子得到“根-右-左”的顺序最后反转结果就得到“左-右-根”。还有层次遍历本质上就是BFS用队列。模板代码如下vectorvectorint levelOrder(TreeNode* root) { vectorvectorint res; if (!root) return res; queueTreeNode* q; q.push(root); while (!q.empty()) { int sz q.size(); vectorint level; for (int i 0; i sz; 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); } res.push_back(level); } return res; }这个for循环次数等于当前队列大小的写法是层次遍历区分“每一层”的关键。这周我在“二叉树的右视图”“填充每个节点的下一个右侧节点指针”“二叉树的最大宽度”三道题里都用到了这个模板只是每层的处理逻辑稍作变化整体结构完全一致。2.3 动态规划的状态定义与转移方程推导动态规划一直是很多人的老大难第十五周我也不例外。但这一轮复盘的收获在于我终于总结出了一套相对稳定的推导流程第一步先想清楚dp[i]或者dp[i][j]代表什么含义这是最重要的状态定义错了后面全错。第二步找转移关系思考dp[i]是怎么从dp[i-1]、dp[i-2]或者更早的状态得到的。第三步确定初始条件和边界。第四步返回目标状态。以“最长有效括号”为例。题目给一个只包含(和)的字符串求最长有效括号子串长度。暴力做法是每个起点枚举每次判断合法性O(n^2)。dp做法就优雅很多定义dp[i]为以第i个字符结尾的最长有效括号长度。如果s[i]是左括号那么它不可能作为有效子串的结尾dp[i]0。如果s[i]是右括号就需要分情况当s[i-1]是左括号它俩配成一对dp[i] dp[i-2] 2当s[i-1]也是右括号就要看i-dp[i-1]-1这个位置是否是左括号如果匹配dp[i] dp[i-1] 2 dp[i - dp[i-1] - 2]。这个推导过程思路本身不复杂但如果状态定义不够清晰很容易糊涂。我的建议是不要硬背转移方程而是画一个字符串索引的示意图把“以i结尾”这个边界条件反复标注清楚。第十五周我在白纸上把最长有效括号、最长回文子串、编辑距离这三道经典题从头到尾推导了一遍做完之后对“状态从哪来”有了明显更深的体感。3. 复盘方法论让做过的题变成自己的武器3.1 一刷二刷三刷的时间节奏第十五周最大的变化是我建立了更明确的刷题节奏第一次刷题只求理解思路可以看题解但看完后要关掉答案自己写一遍三天后第二次刷独立完成重点检查有没有卡壳两周后第三次刷限时十分钟出思路二十分钟调试完成。这个节奏的好处是它把“会不会做”和“能不能记住”分开了。很多人刷题遇到的问题是当时看懂了答案觉得简单过两周连题目讲什么都忘了。这就是缺少间隔重复的后果。记忆曲线的原理大家都知道但落到刷题里很多人就是不愿意回头做旧题。第十五周我强制自己把二刷率维持在30%以上效果立竿见影面试风格的限时练习里读题后想思路的时间明显短了。3.2 错题本的正确记录姿势之前我记错题本的方式是把题号、题名、题解粘进去美其名曰整理实际上不会再看第二遍。这周我换了方式每道错题只记录三样东西错误原因是边界条件漏了还是转移方程写错还是数据结构选错思考卡点卡在哪一步为什么没想到这一步正确思路的一句话版本方便三天后自我提问比如有一道“乘积最大子数组”我犯的错误是只维护了当前最大乘积却忽略了负数乘负数可能变成最大值的场景。我当时的错误原因记录是“只维护了最大状态没有维护最小状态”。三天后再看一眼就想起正负切换这个关键点。错题的记录不需要长但要直击痛点。我见过很多人错题本写得很漂亮各种颜色的高亮但核心信息都被淹没在排版里。刷题笔记是给自己看的不是手账简洁、能回忆起当时的坑就好。3.3 白纸复述和讲题输出这周我试了一个新方法每天挑一道题按“题目目标-核心思路-复杂度分析-边界情况”四步在白纸上复述不是写代码而是像面试时讲思路一样说出来。这个方法远比想象中有效。因为“写代码”可以依赖调试器修正但“讲思路”不行思路不清晰当场就卡住。比如讲“盛最多水的容器”这道题我一开始只能说出来“双指针移动”但问到为什么移动较短的边就含糊了。后来想明白了容器高度由短板决定移动短板可能找到更高的新板移动长板高度只会更差。这一句话想清楚之后双指针就不是背下来的而是推出来的。第十五周这个“讲题”动作大概花了每天二十分钟但对长期记忆的帮助比多刷十道新题还大。3.4 模板代码沉淀与分类整理随着刷题量增加我建了一份自己的“模板库”文档按数据结构分类每类收录两到三个核心模板。这份模板不是从网上复制粘贴的大而全版本而是自己手写并注释过的精简版。比如二分查找模板我记录了自己最常用的左闭右开写法int lower_bound(vectorint nums, int target) { int left 0, right nums.size(); // 左闭右开 while (left right) { int mid left (right - left) / 2; if (nums[mid] target) left mid 1; else right mid; } return left; }关键注释只有一句“最终left指向第一个不小于target的位置”。这个版本避免了死循环也统一了搜索区间。模板的意义不是让你死记硬背而是把每一类题里共通的骨架提炼出来遇到变体题时只需要在该变的地方变。4. 本周踩过的坑与排查技巧实录4.1 TLE超时问题从暴力到优化的实战记录第十五周我在“和为K的子数组”这道题上交过一笔昂贵的学费。最直接的思路是枚举所有子数组并求和O(n^3)心想数据范围小说不定能过结果一提交直接超时。第二次优化成前缀和数组预处理前缀和数组之后枚举子数组起点和终点O(n^2)可算但在一组大数据上还是超时。这时候我才意识到这题的正确打开方式是“前缀和 哈希表”。核心思路是子数组和等于k等价于前缀和数组中当前前缀和减去目标k之后的值在之前出现过。用一个哈希表记录每个前缀和出现的次数一边遍历一边查询时间复杂度就降到了O(n)。int subarraySum(vectorint nums, int k) { unordered_mapint, int mp; mp[0] 1; int sum 0, res 0; for (int num : nums) { sum num; if (mp.count(sum - k)) res mp[sum - k]; mp[sum]; } return res; }这个坑给我的教训很直接不要用“先暴力再优化”的思路应对有明确时间限制的题目。如果题目涉及“连续子数组”并且需要统计次数应该第一时间往“前缀和 哈希表”这个组合想。暴力保平安在小数据量下是对的但在竞赛和面试场景下通常目标解法就是最优解附近。4.2 边界条件与数组越界复盘第十五周的错题里有接近三分之一的错误源自身边界条件没处理好。一个典型案例是“最长回文子串”。我用中心扩展法实现逻辑本身不复杂但扩展的判断条件写成了 while (left 0 right n)忘记了左右指针还要满足 left right导致奇数长度和偶数长度区分的时候偶尔会出现越界访问。这种问题最坑的地方在于小数据量测试根本测不出来一旦n1或者回文串出现在字符串尾部才会暴露。排查这类问题我的经验是写完代码之后立刻用三个极端用例做自测分别是数组长度n1、n2、以及目标结果出现在数组最末端。这三个用例一旦全部通过越界问题基本能堵住百分之八十。4.3 贪心思维的常见陷阱有一类题目会故意引导你往贪心方向想但正确答案却需要动态规划。第十五周我踩的“跳跃游戏II”就是很好的例子。题目要求从数组第一个位置出发跳到最后一个位置的最少步数。一开始我试图维护“当前能到达的最远距离”每次能跳多远就跳多远结果在某些用例上答案不对。原因在于局部最优不等于全局最优。这道题正确的贪心解法确实成立但需要维护的是“当前步数能覆盖到的区间”以及“下一步能覆盖到的最远区间”每次在可覆盖的区间内挑一个能跳得最远的位置而不是盲目地第一次能跳多远就跳多远。区分贪心和动态规划的一个直观标准是如果当前选择会限制之后的选项且需要全局考虑通常不是贪心如果当前选择只影响到一个局部结果且每一步的最优能推出全局最优才适合贪心。4.4 心态管理当刷题从激情变成了任务第十五周我对心态问题有了新的体感。起因是周三晚上做一道中等难度的动态规划题前前后后写了40分钟提交后25个用例只过了17个状态转移方程出问题一时间既生气又沮丧。过去遇到这种情况我一般会硬刚到底但这次我选择停下来去楼下的便利店买瓶水回来之后把题目重新读了三遍画了张状态转移的示意图问题一下就清楚了。后来我反思刷题过程中的情绪管理本质上和算法设计中的“剪枝”是一回事。当你在错误的方向上继续投入边际收益是递减的甚至为负。与其硬撑着写不如给自己设定一个“冷静时间”连续卡30分钟就起身走一圈回来不直接看题解而是把题面当新题重新思考一遍。这个方法第十五周帮我解决了不少原本可能要翻答案才能解的题。5. 十五周刷题实践中的工具与资料取舍5.1 刷题平台的搭配使用我平时主要用一个主刷题平台完成日常练习周末会额外用一个在线评测平台做限时模拟。两个平台的侧重点不一样日常主刷题平台的题解区讨论氛围比较好适合看别人的思路和优化方案周末模拟平台更接近竞赛场景时间卡得更紧可以训练在压力下快速定位考点。但需要注意一点不要同时在六七个平台注册账号每天换着刷。平台太多会导致知识碎片化这套系统做几道那套系统做几道最后连自己的刷题记录都找不全。选定一个主要平台把所有专题的题单都建立在这个平台上历史记录、错题、收藏夹都在同一个地方复盘时才不会到处翻。5.2 本地笔记工具与代码管理配置第十五周我开始把笔记从零散的备忘录迁移到本地Markdown文件并且统一了文件命名规则按“日期-专题-题目名”的格式存储。这个习惯的建立让复盘时找历史笔记的效率提高了很多。配合本地代码管理每个专题一个目录题解按文件名存成单独的Markdown文件题目标题用链接指向原始题目代码段直接嵌入。这样一份笔记就是一份可检索的题解库配合搜索功能查找“之前那道区间合并的题”只需要搜索关键词而不是滚动屏幕翻记录。5.3 资料选择的“少而精”原则这周我清理了一批收藏从未看的算法资料和关注列表。之前总喜欢囤各种“算法模板大全”“一百天刷题计划”实际上每份资料都只翻了前几页。第十五周之后我的原则是一份系统性的教材 一个题单网站 一个交流社区其他的基本不看。系统性教材用来建立知识框架题单网站用来安排每周练习范围交流社区用来在卡壳时查看别人的思路。三个渠道各有分工信息冗余少了专注度自然就上来了。刷题到了第十五周真正稀缺的已经不是资料而是对已学内容的消化能力。6. 关于第十五周刷题总结的一些个人体会写到这里第十五周刷题总结也算接近尾声了。如果只让我保留一条这周最重要的心得我会选这句话刷题的数量远没有刷题的质量重要而刷题质量的核心标志是你能不能在不看答案、没有提示的情况下把一道题的来龙去脉讲清楚。“讲清楚”三个字看起来简单做起来很考验人。它要求你不仅知道解法的步骤还要知道为什么选这个解法为什么这里的边界条件是开区间而不是闭区间为什么这个算法的时间复杂度更优。这些问题在写代码的时候很容易被忽略但在面试和真实工程场景里恰恰是判断一个人是不是真的理解了问题的关键。另一个体会是刷题不是冲刺跑而是一场漫长的马拉松。第十五周的我已经没有了第一周那种“今天必须刷十道题”的冲劲日子反而变得更加平淡和规律。但这种平淡里有一种踏实的进步感。当你发现自己可以在15分钟内默写出二叉树迭代遍历的完整模板可以在一分钟内判断一道题属于哪种模型你会感受到前期积累的回报正在一点点显现。最后再分享一个小技巧每周结束之后花半小时把这周所有做过的题过一遍目录不用写代码只看题目名称然后在心里给每道题标一个“掌握”或“不熟”的标签。这个动作看着简单却能非常清晰地暴露你的薄弱点是下周计划的起点。第十五周我就是靠这个目录检查发现二叉树部分掌握得比想象中好而动态规划的状态定义还需要继续练于是把第十六周的主攻方向确定为“状态定义专项训练”。
返回列表