恒美微站
首页
关于我们
建站服务
主题模板
案例展示
资讯中心
联系我们
刷题第5天:动态规划、链表与哈希表的踩坑复盘与节奏管理
首页
资讯中心
/
刷题第5天:动态规划、链表与哈希表的踩坑复盘与节奏管理
刷题第5天:动态规划、链表与哈希表的踩坑复盘与节奏管理
发布时间:2026/10/16 7:47:43
入坑第5天每日算法题的节奏感与踩坑实录坚持每天刷一道算法题到第5天是个微妙的节点。第一天多半是打鸡血第二天靠新鲜感撑着到第三天、第四天如果选题不够讲究、难度曲线太陡很多人就开始打退堂鼓了。而第5天恰好是一个检验这套方法能不能长期跑通的临界点——题量不多不少刚能看出点规律又还没到质变的程度。如果你正在这个阶段这篇文章应该能给你一些实实在在的参考。这套“每日算法题”系列的初衷很简单不追求题海战术而是每天花30到60分钟吃透一道题的核心逻辑并记录下踩坑和思考过程。选题上覆盖了字符串、链表、动态规划、回溯、位运算等常考类型第5天的题单我选了动态规划、链表和哈希表三个方向难度控制在LeetCode中等偏下——这个梯度既不会让人想放弃又能逼着你动脑子。这一篇博文除了题解本身我还会把第5天这个节点上自己摸索出的节奏管理、保活方法和避坑清单一并写出来。不管你是刚起步的初学者还是已经刷了一两百题的老手如果你也想建立一套可持续的刷题习惯这篇内容值得你花20分钟读完。1. 前5天如何安排刷题从“随便刷”到“有体系”1.1 为什么第5天最容易放弃我自己经历过很多次“刷题计划暴毙”。总结下来规律惊人的一致第1天热情高涨第2天再坚持一下第3天开始怀疑人生第4天摸鱼第5天基本就断了。原因并不复杂前几天的题目选择往往很随意——今天看这道题顺眼就刷明天觉得那道题简单就划掉。没有体系的知识点覆盖没有难度控线也没有错题复盘的闭环。刷了两三天以后你会发现自己除了“做过几道题”之外什么积累都没有自然就丧失了继续的动力。到了第5天如果想打破这个循环必须要做的第一件事就是调整策略把“想到什么刷什么”改成“按专题推进难度梯度控制复盘归档”。不需要多么精密的规划系统只需要一张能坚持执行的题单。1.2 我连续5天使用的固定套路给各位一个我在用的极简方案每天循环执行稳定性非常高筛选当日题目每天筛选1道中等难度题如果当天时间紧张就用1道简单题保底但绝不让当天空窗。限定时间盒做题前设一个45分钟的倒计时。45分钟内想不出来直接看题解绝不硬磕超过90分钟。记录分析过程不管做没做出来都要记录当时的思考和卡点。这个记录是第5天之后最值钱的资产。每天归档复盘做错的题用简短笔记记下“为什么没想到”和“更好的切入点”。这套流程看起来朴素但它的核心价值在于不管题解出来没解出来每一天的付出都会沉淀成记录和笔记而不是水花打一下就消失了。1.3 第5天的三道题与安排逻辑第5天我安排了这样三道题专题题目类型核心考察点实际用时动态规划背包变体问题状态定义与转移方程的推导约50分钟链表环形链表检测快慢指针与边界条件约25分钟哈希表前 K 个高频元素分区思想与堆的取舍约38分钟简单解释一下为什么这么搭配动态规划是重头戏需要整块时间环形链表属于“会者不难”的经典题适合建立信心前K个高频元素则用来训练工程思维——这类题在现场面试和系统设计中都有衍生场景。2. 核心题目深度拆解三道题背后的思考链2.1 背包变体题从“我不会动态规划”到“状态其实很简单”先说这天的重头戏。题目是这样描述的有一个容量为C的背包和一些物品每件物品有自己的重量w[i]和价值v[i]每个物品最多只能取一次求在不超过背包容量的前提下能装入的最大总价值。经典的0-1背包问题很多人在面试前都背过模板但真正上手写的时候还是会卡住。我先把这道题的价值说透它表面上是背包问题实际上考察的是“如何用状态描述一个决策过程”。这是一个所有动态规划题都会遇到的共通能力练好这道题后面再做路径类、序列类的动态规划都会顺手很多。解题的切入点是这样一套固定流程第一步明确搜索空间。对所有物品做决定时第i个物品只有两个选择装进去还是不装进去。如果把所有组合都枚举一遍有2的n次方种方案显然不可行。这是需要动态规划的原因。第二步定义状态。我用dp[i][j]表示考虑前i个物品、背包容量为j时能获得的最大价值。这里有两个维度第一个维度是“已处理的物品数”第二个维度是“已用容量”两者合在一起恰好能唯一确定一个子问题。第三步写状态转移方程。对第i个物品如果重量w[i]大于当前容量j那只能不装否则在装与不装之间取最大值。用公式表达就是if (w[i] j) { dp[i][j] dp[i - 1][j]; } else { dp[i][j] Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] v[i]); }第四步决定遍历顺序。我习惯先遍历物品再遍历容量容量从0遍历到C。这样每一个状态都能依赖到上一行的已有状态方向不会乱。如果你跟着我的思路自己推导一遍会发现这道题真正的难点并不在公式推导而在于“能不能想到用二维状态来描述过程”。想通了这一点后续就是机械地套模板。不过上面这个版本是二维数组写法空间复杂度是O(n*C)其中n是物品数。在实际面试中如果能优化到O(C)会是很明显的加分项。我当时先把二维版本写通之后花了额外10分钟做空间优化观察转移方程发现dp[i][j]只与dp[i-1][j]和dp[i-1][j-w[i]]有关也就是说只依赖上一行。这时候可以把二维数组压缩成一维数组用一维数组滚动更新。但这里有个非常经典的陷阱必须提醒各位——容量j必须从大到小遍历。为什么因为如果j从小到大遍历那在更新dp[j]的时候dp[j-w[i]]这一项可能已经被本轮更新过了也就是说第i个物品被重复取了会发生“同一个物品装了两次”的问题。而0-1背包每个物品最多取一次所以必须倒序遍历保证更新dp[j]时使用的dp[j-w[i]]是上一轮的旧值。这个倒序遍历的细节很多教科书都会写但几乎不会解释为什么。我在第5天重新梳理的时候把正序和倒序都跑了一遍用容量为5、一个重量为3价值为4的物品做测试正序遍历果然算出了两倍价值。纸上得来终觉浅绝知此事要躬行这句话放在刷算法题上再合适不过。2.2 环形链表检测快慢指针的边界问题第二道题是环形链表检测题目描述很简洁给定一个链表判断链表中是否有环。这道题有几种常见解法。最简单的思路是用一个哈希集合记录每个访问过的节点如果当前节点的next已经存在于集合中说明有环。这个做法空间复杂度是O(n)不是最优解。最优解是快慢指针法维护两个指针慢指针每次走一步快指针每次走两步如果链表有环快指针和慢指针一定会在环中相遇如果无环快指针会先到达链表末尾。快慢指针的正确性证明逻辑是当慢指针进入环时快指针已经在环内某处此时两者距离差为k。由于每次快指针比慢指针多走一步两者的相对距离会每次缩小1因此最多走k步后两者必定相遇。这道题我在写的时候没有卡在主逻辑上反而卡在了边界判断。边界条件有三个易错点空链表或只有一个节点的链表直接返回false快指针移动两步时要确保fast不为null且fast.next不为null循环条件应该写成slow ! fast而不是while循环内部一层层判断否则代码很难读。给你一段我当时提交的通过代码可以作为参考function hasCycle(head) { if (!head || !head.next) return false; let slow head; let fast head.next; while (slow ! fast) { if (!fast || !fast.next) return false; slow slow.next; fast fast.next.next; } return true; }我最初写的时候把fast初始化为head而不是head.next就会出现一种情况在无环链表里fast和slow永远不会相遇但在有环链表里没问题。虽然不会报错但代码的可读性和行为不够直观。后来参考了一些最佳实践统一把fast初始化为head.next这样while循环条件就非常干净。这里说一个值得养成的习惯链表类的题边界条件应该在写主逻辑之前先想清楚而不是靠测试用例逼你补丁式修复。我见过很多开发者写链表题时主逻辑两分钟就写完了但for循环里防空的if语句写了七八个代码又丑又容易漏。边界条件想清楚写出来的代码才像样。2.3 前K个高频元素哈希表统计与堆排序的搭配第三道题是从一个非空整数数组中返回出现频率前K高的元素。题目本身不难但解法很多从最笨的排序法到最优的前K堆方案涉及复杂度分析和工程实践的权衡。这道题最容易被忽略的是“不能用普通排序解决一切问题”这个点。如果你先哈希表统计所有元素的频率再对所有频率排序然后取前K个这个做法能通过但时间复杂度是O(n log n)空间复杂度也是O(n)。在没有额外约束时能过可一旦数据规模大了就不够用了。更优的做法是维护一个大小为K的小顶堆。具体步骤是这样的先用HashMap统计每个数字出现的次数然后遍历这个HashMap的键值对依次加入一个小顶堆中如果堆的大小超过K就把堆顶元素也就是当前堆中最小的频次元素弹出遍历结束后堆中剩下的K个元素就是频率最高的K个元素。为什么用“小顶堆”而不是“大顶堆”这个设计很关键因为我们只关心最大的K个元素如果每次有新的候选元素只要它的频率大于堆顶的最小频率就需要把它插进来取代堆顶。用小顶堆可以保证堆顶始终是“当前K个最大候选里的最小者”这样每次淘汰掉频率最小的那个效率最高。用JavaScript实现堆时我一般直接手写一个最小堆的插入和弹出逻辑大概50多行代码。不额外引第三方库因为面试时一般也不允许。function topKFrequent(nums, k) { const map new Map(); nums.forEach(n map.set(n, (map.get(n) || 0) 1)); function pushHeap(heap, item) { heap.push(item); let i heap.length - 1; while (i 0) { const parent Math.floor((i - 1) / 2); if (heap[parent].freq heap[i].freq) break; [heap[parent], heap[i]] [heap[i], heap[parent]]; i parent; } } function popHeap(heap) { if (heap.length 1) return heap.pop(); const top heap[0]; heap[0] heap.pop(); let i 0; while (true) { let smallest i; const left i * 2 1; const right i * 2 2; if (left heap.length heap[left].freq heap[smallest].freq) smallest left; if (right heap.length heap[right].freq heap[smallest].freq) smallest right; if (smallest i) break; [heap[smallest], heap[i]] [heap[i], heap[smallest]]; i smallest; } return top; } const heap []; for (const [num, freq] of map) { pushHeap(heap, { num, freq }); if (heap.length k) popHeap(heap); } return heap.map(item item.num); }这段代码里堆的插入和删除操作都是O(log K)所以整体时间复杂度是O(n log K)当K远小于n时比全排序方案有明显优势。这道题自己也做一个扩展联想如果你面对的是超大流量场景HashMap统计的结果可以放到内存里再配合全局堆或者分桶策略来处理这是另一个话题了。3. 刷题环境配置把工具调到顺手效率至少翻倍3.1 本地调试环境搭建很多初学者喜欢直接在网页编辑器里刷题代码写完一提交出了错再改。这样效率其实很低因为在线编辑器没有断点调试只能靠console.log打印。所以我建议本地搭一个轻量调试环境配合编辑器使用。我的本地环境非常简单系统里装了Node.js版本20以上配合VS Code。每个新题单独建一个文件夹里面放一个solution.js直接node solution.js就能看到输出。如果你想跟测试用例做断言比对可以再加一个test.js用Node内置的assert模块跑用例。这样做的核心价值是可以打断点。VS Code的JavaScript调试器支持在solution.js里添加断点一行一行地看每个变量在每一轮循环里的变化。这比靠肉眼顺着代码推敲排查要快得多。对链表、树这类结构型题目尤其有用因为你可以直接在调试面板里展开节点对象查看next指针和val字段。3.2 自动化生成题解模板刷题到第5天我已经受够了每天手工创建目录、复制模板文件。于是我花了一点时间写了一个初始化脚本每次运行只需输入题目编号和题目名就能自动生成一个包含基础模板的solution文件、测试文件和笔记文件。模板的骨架是这样的# template_generator.py import os import sys def create_daily_entry(problem_id, title): folder f{problem_id}_{title} os.makedirs(folder, exist_okTrue) solution_content /** * {id} - {title} * 日期: {date} * 难度: 中等 * 思路: */ function solution(data) { // TODO } // 测试用例 const inputs []; const expected []; for (let i 0; i inputs.length; i) {{ const result solution(inputs[i]); const pass JSON.stringify(result) JSON.stringify(expected[i]); console.log(case {{i}}: {{pass ? PASS : FAIL}}); }} .format(idproblem_id, titletitle, datedatetime.date.today()) with open(os.path.join(folder, solution.js), w) as f: f.write(solution_content) print(fCreated folder: {folder}) if __name__ __main__: problem_id sys.argv[1] title sys.argv[2] create_daily_entry(problem_id, title)有了这个脚本之后每天的进入成本就从“开网页新建文件”变成了一条命令操作。别小看这十几秒的差异。人脑的习惯回路对摩擦成本非常敏感降低启动阻力是坚持一项日常活动最重要的杠杆。3.3 笔记与代码风格统一另一个值得提前做的工作是统一代码风格。我在第5天开始检查前几天的代码时发现早期代码里的变量命名和缩进风格不那么一致回头读的时候需要额外花时间适应。这里建议大家从第1天就定下几条简单的规则并坚持执行比如变量名一律使用准确的英文例如用index而不是idx这种缩写循环变量用i、j、k但语义变量用当前的含义描述复杂逻辑加一两行注释说明“这一步是在处理什么边界”每个题解文件头写清楚题目、日期、用时、是否一次通过。统一风格看起来无关紧要但它能带来的真正的价值是当你第30天、第50天回头翻笔记时不需要费劲去解读自己过去的代码。你写的题解本质上是一本给自己看的工程日志工整是最基本的要求。4. 错题复盘体系把“不会做”变成“下一次能秒”4.1 复盘不只是抄题解第5天我重新整理了前4天的错题发现一个共性我做错的题做的时候觉得“看了答案恍然大悟”但过两天再遇到同类型的题依然不会。这其实是无效刷题的典型症状。关键在于理解题解和能把题解复现出来是两个完全不同的能力层次。看着答案能读懂只说明你的智商在线而真正掌握一道题是指你在没有答案的情况下看到题目特征就能判断出该用什么算法并且能独立推导出转移方程或者指针移动逻辑。所以我对“复盘”做了一个动作拆分每次错题复盘必须包含三个层次第一层看题解理解答案的逻辑第二层合上题解自己从头推导一遍完整过程第三层次日再速刷同样类型的题目验证自己是否真的掌握了这个套路。前4天我基本只做到第一层所以知识留存率很低。第5天开始把第二层和第三层补上感觉记忆和理解明显加深了。4.2 用“类型标签”来分类错题在第5天整理错题笔记的时候我把前几天的题目按“考察类型”重新打了一遍标签。不要按题目名称分类而应该按“解题方法论”分类比如动态规划背包问题、最长上升子序列、编辑距离快慢指针环形链表、寻找中点、链表中倒数第K个节点哈希表辅助两数之和三数之和、频率统计类问题回溯组合、排列、子集问题二分答案搜索旋转排序数组、爱吃香蕉的珂珂这些标签的价值在于它们能帮你快速识别一道新题的内在结构。比如看到“求满足条件的最值”时你脑海里会多跳出一个“可能需要二分枚举答案”的判断分支。这种条件反射不是靠天赋而是靠反复的类型标记和归类训练出来的。4.3 建立“隔日回刷”机制回刷是很多刷题博主不太提、但极其关键的环节。我见过不少人刷了三百道题到面试时依然紧张到一题不会原因就是不回刷。我的做法是隔日回刷。具体来说第1天做完题记录是否通过第3天重新做一遍没通过的题第7天再做一遍如果这次能独立通过并且能说出核心思路和边界条件就从待回刷清单中移出。回刷时不许看之前的代码和题解从零开始写。这个过程就是模拟面试状态会比“刷新题”更有压力但也更有效。如果你觉得回刷占用了刷题量我可以负责任地告诉你回刷旧题和新题的价值大概是可以对半开的。它在帮你通向“熟练”这个目标上比盲目刷新题要高效得多。4.4 如何高效写短期记忆笔记整理错题后每道题我会写“一句话思路”放在最顶部。这招是跟一位经验非常丰富的前辈学的很好用我不藏私分享出来。比如背包问题一句话思路是先物品再容量、容量倒序、装或不装取最大。环形链表的一句话思路是快慢相遇有环快指针先到边界则无环。前K高频的一句话思路是小顶堆固定大小弹最小留最大。别小看这种一句话式的压缩表达它有两个好处一是写的时候强迫你提炼核心你对这道题的理解会被迫变得更清晰二是回头复习的时候你只需要看一句话就能唤醒整个记忆不用翻一整页代码。5. 常见问题与排坑速查第5天最容易踩的坑5.1 一天能刷多道题吗要不要刷Hard题经常看到有人在打卡群里晒“一天刷了10道题”这种刷法的效果我觉得需要打个问号。刷10道且都是简单题不如精刷1道中等题因为后者能让你把注意力放在思考深度上而前者容易变成机械重复。第5天这个节点我建议以中等题为主线。如果你遇到Hard题也不是不能碰但要有心理预期2小时到3小时的硬磕时间不一定有回报。如果一小时后没有任何思路进展果断转去看题解然后认真复盘。这时候看题解的收获比你盲写三小时大得多。5.2 卡了很久的感想45分钟原则在1.2节我已经提到了时间盒的概念这里再展开讲讲。45分钟原则是我实测后觉得比较有效的时间管理策略一道题如果在45分钟之内没有任何有效的推进说明当前的思考路径大概率有问题要么是知识点缺失要么是需要一个新的角度切入此时最有效的动作就是直接看题解。这样做并不会让你变得更依赖题解反而会保护你的刷题意愿。如果你每天浪费两三个小时在一道题上第二天会条件反射地抗拒刷题而如果你每天只花45分钟在一道题上即使没做出来你也会觉得“其实还可以接受”第二天依然愿意继续。5.3 一次关键词引发的血案注意审题第5天做题时我犯了一个特别典型的错误背包变体那题题目描述中说了“每件物品只能选一次”我当时脑子里默认成了“可以重复选择”于是递推的时候反复经过了它。检查发现结果偏大回看题目时才发现自己忽略了一个关键词。这个教训非常真实也非常普遍。所以我把“审题三件事”写在桌面便签上第一确认每个变量的取值范围第二确认取值的限制条件比如无重复、不可重复取、可重复取第三确认题目要求输出的是最大值、最小值还是方案本身。这三点任何一个偏差都可能导致整体推倒重来。5.4 怎样在刷题中坚持记录而不觉得繁琐我承认每天给每道题写一篇详细题解是很累的很难坚持。所以我的方案是“分层记录”通过了的简单题只记录类型和一句话思路没通过的题或好题才写详细的过程笔记和复盘。这样既能保存关键学习成果又不至于把每天的精力消耗在记录本身。如果你刚开始刷题我建议你也采用这种分层策略。详细笔记的价值在于错题和好题简单题流水账式的记录在后期几乎不会回看不要被“完美主义”绑架。5.5 排坑速查表为了方便你按图索骥我把第5天前后遇到的典型问题做了一个速查表格。这些问题不分先后都是真实的踩坑记录症状可能原因解决方案动态规划结果偏大正序遍历容量导致物品重复使用容量倒序遍历链表指针报空指针异常快指针移动时未判空循环开头校验fast和fast.next快慢指针无法相遇fast初始化位置错误改为head.next开始堆中元素数量不对弹出操作和插入操作的顺序颠倒先插入再判断是否超K个提交超时排序方式不够优化改用堆或分桶做过的题隔周不会缺乏回刷机制隔日回刷、周次回刷5.6 适合第5天的其他练习题第5天如果状态不错从题单中抽一两个小时再练一道巩固题我推荐下面这些同类型的组合练习在形成结构化理解方面效果很突出最长回文子串检验动态规划或中心扩展法反转链表系列如果你的链表还不太熟这个系列可以做到滚瓜烂熟两数之和从暴力到哈希表的经典进阶路径岛屿数量打好DFS/BFS的地基每一天的题量不需要多但花同样的时间要逼自己去想一想“如果我把条件改一下这题还能用同样的方法解吗”这种扩展问题。6. 刷题节奏与心态管理做到第5天之后怎么办6.1 为什么“可持续”比“高产出”重要第5天是一个分水岭。能坚持到第5天说明你没有在最初的热情降温期放弃。接下来最危险的时间点是第10天到第15天之间的疲劳期。这时候你可能会发现“为什么每天花45分钟刷题却没有感觉到明显进步”。我想说这是一种错觉。刷题能力的成长很少是“线性上升”的更多时候是一个接一个的“平台期”加“跳跃期”。你会在某一天突然发现一道以前觉得很难的题目现在写得行云流水这种跳跃往往发生在积累到一定量之后。第5天远还没到质变点你需要做的只是保持节奏。最有效的做法是给自己设定一个最低保底量每天至少写一道题的主函数哪怕这个题特别简单哪怕这题你之前做过。只要不让连续空窗超过一天你就能维持手感。算法能力本质上是肌肉记忆加思维方式训练如果断档超过48小时手感和思维连接都会出现明显的退坡。6.2 从“一题”到“一组”的专题循环思路第5天之后我建议你把“每天一题”升级为“三天一组”的模式。这样循序渐进的效果比单纯每天换一个专题要好得多。比如这三天集中做“双指针”那你就把两数之和、三数之和、接雨水之类的题放在一起集中训练。三天时间足够让你在这一个类型上形成比较稳定的直觉。下一组三天再做“动态规划”你就把背包问题、打家劫舍、最长递增子序列这类题放在一起。一个专题练完进入下一个专题前花半天把前一个专题的笔记回扫一遍重点看那些“卡住时有提示才有思路”的题。这种专题循环会让你的知识地图变得越来越清晰。相反如果每天刷一个完全不相关的专题你的脑海中很难建立起知识点之间的联系就会状态不稳定。6.3 关于面试准备的实话最后说一点题外话。很多人刷题是为了准备面试那我想提醒的是面试考察的不仅仅是你会不会做这道题更重要的是你“如何思考”这道题。面试官更愿意听你说“我先想到了暴力解然后发现这里存在重复计算所以我用哈希表来优化”而不是看见你默写出一个最优解后一言不发。所以从第5天开始你可以有意识地练习“说题”每做完一道题把思路、为什么选这个算法、复杂度多少、有哪些边界条件这四步口头梳理一遍。可以对着镜子说也可以打开手机录音说给自己听。这个习惯到了真实面试场景中价值极大而且现在不练的话后面想要临时抱佛脚是不太可能的。这4天多的时间刷下来我最直观的感受是算法刷题逐渐从一个负担变成了一种节奏。每天30到60分钟像吃饭喝水一样规律。到现在第5天我不会再想“今天要不要刷题”而只想“今天刷哪道题”。如果你也想把这件事长期坚持下去不妨先别管能刷多少题把这道题当作一个固定日程安排进你的生活然后观察几周之后的自己——大概率会有意想不到的变化。