刷算法题这件事,大家心态很容易走两个极端:要么觉得这是三个月甚至半年的大工程,要么就一头扎进题海,每天随机抽着刷,刷了两周发现还在原题徘徊。我见过太多人一年前就说要把Hot100刷完,结果半年过去了还停留在第一页。这次干脆换个思路,用一份7天逐题清单强制自己集中突破,把每天的任务钉死到具体题型和具体题号上,不给自己犹豫和随机浏览的机会。
先说这篇文章是什么,以及适合谁。它是一份把LeetCode Hot100拆成7天执行计划的逐题清单,覆盖数组、哈希表、双指针、链表、栈、二叉树、图论、回溯、动态规划这些核心板块,每天大概14到15题。适合已经具备基本编码能力、想在短时间内把职场算法面试的高频题型系统过一遍的开发者,也适合反复刷题但一直没形成体系、总觉得自己“刷了个寂寞”的人。七天刷完一百题,表面看量很大,但把它拆到每天、每组、每类模板之后,你会发现真正需要独立记忆的东西,其实远没有一百份。
1. 为什么是Hot100,又为什么是7天
1.1 Hot100的价值并不在于“题目本身”
很多人都问过一个问题:为什么非得刷Hot100,刷别的题不行吗?我的回答是:你可以刷任何一套系统整理的题单,但Hot100是目前覆盖面、难度梯度和标签平衡做得最均衡的一套集合。
它把题目按数据结构、经典算法、常见变形三个维度做了筛选,既不偏向高难竞赛题,也不停留在“Hello World”级别的语法治练。像双指针、滑动窗口、单调栈、树的遍历、动态规划这些面试必须掌握的模板,都涵盖了。更关键的是,同一个模板往往被多个题目反复使用,你只要真正吃透一个模式,就能一次性打掉三四个题。
选Hot100还有一层现实考量:它的讨论区和题解资源最丰富,对自学者来说遇到卡点容易找到参考讲解,不用因为一道题卡太久而拖垮整天的计划。
1.2 7天计划的拆解逻辑
7天不是拍脑袋定的,它对应的是一个典型的“聚焦冲刺周期”。人的短期记忆刷新和强化刚好在一个星期这个尺度上,周一到周四学新模板,周五到周日负责巩固和串联,这个节奏天然适合结构化的学习计划。
每天按数据结构和算法思想分组,是为了把做题的思维成本降到最低。比如把滑动窗口、前缀和、哈希表都堆在一天里,你会发现自己一直在切换解决问题的底层思维框架,状态很容易被打断。而按照主题分组,会让彼此相邻的题共享同一套套路和复杂度分析方式,思考负担会小很多。
从量上看,100题除以7天大约是每天15题。如果每题平均需要30到40分钟,一天就是8小时左右。多数人很难做到这种强度,所以我在清单里人为设置了难度梯度,把经典的简单题和中等题按比例分配,保证每天都有“热身题”和“压轴题”,同时留出两三道选做作为Buffer,防止因为卡在一道难题上导致全天内容崩盘。
2. 7天逐题清单与每日核心任务
下面这个清单是按主题和模板划分的,题目顺序也尽量从易到难排列。每天给你先说明当天要掌握的核心模板,再列具体题目,最后挑一两道代表题做思路拆解,方便你沿着同一套解法逻辑推进。
2.1 Day1 数组与哈希表:空间换时间思想的集中训练
核心模板:哈希表索引、排序后合并、去重判断
Day1 题组:
- 两数之和
- 三数之和
- 字母异位词分组
- 最长连续序列
- 合并区间
- 移动零
- 只出现一次的数字
这个组合第一天没有安排太难的题,是因为要先建立“看到数组题先想是否可以借助额外空间降低时间复杂度”的条件反射。很多人刷题容易卡在惯性思路里,拿到数组题就开始双循环暴解,时间复杂度到了O(n^2)还没察觉。
“两数之和”就是空间换时间最经典的示范。暴解是两重循环,而用哈希表把已经遍历过的值存起来,每次只需要查target - nums[i]是否存在,时间复杂度直接降到O(n)。这个思想会贯穿整个七天,后面的“最长连续序列”也依赖哈希表去重和快速判断连续性。
“三数之和”要特别注意一点——它和“两数之和”表面长得像,但解法完全不同。两数之和用哈希表,三数之和是排序加双指针。因为它要求去重,所以必须在排序后通过移动指针跳过重复元素,这一步经常有人忘记,导致结果里多了重复三元组。
提示:第一天最重要的不是把七道题全做对,而是建立起“用空间换时间”这个思维开关。后面的题会反复用到。
2.2 Day2 双指针与滑动窗口:最容易被低估的高频答题模板
核心模板:左右对撞指针、快慢指针、定长与变长滑动窗口
Day2 题组:
- 盛最多水的容器
- 有效三角形的个数(选做)
- 无重复字符的最长子串
- 找到字符串中所有字母异位词
- 最小覆盖子串
- 和为K的子数组
- 移动窗口最大值(选做)
第二天是整轮计划里性价比最高的一天,因为双指针和滑动窗口是面试里极高频的考点,而且一旦掌握模板,很多新题都只是换了个外壳。
“盛最多水的容器”是左右对撞指针的入门题,核心思路是:初始时左右指针指向数组两端,此时底边最长;每次向内移动较短的那条边,因为水的高度由短边决定,只有移动短边才有机会扩大面积。这个思考过程一旦形成,能推出一大堆类似题目。
“无重复字符的最长子串”则是滑动窗口的入门代表作。用左右指针维护一个窗口,右指针不断向右扩展,收到新字符时通过哈希集合判断是否重复;如果重复,左指针就向右收缩,直到窗口内没有重复字符为止。整个过程中记录窗口的最大长度。
“最小覆盖子串”比前几道难很多,也是不少人的噩梦。它需要维护一个need计数表,用窗口内已覆盖的字符数量matched来控制收缩时机。它的价值在于,你如果能独立完成这道题,滑动窗口这一整类问题基本就打通了。这道题建议放在一天状态最好的时候去写。
注意:滑动窗口的核心难点不是怎么写,而是“什么时候左指针收缩”。原则是:窗口内不再满足条件时,收缩左边界;一旦重新满足条件,立刻更新答案并继续扩展右边界。别把这个顺序搞反。
2.3 Day3 链表与栈:不再怕指针操作和单调栈
核心模板:虚拟头结点、快慢指针定位、栈的单调性维护
Day3 题组:
- 反转链表
- 合并两个有序链表
- 删除链表的倒数第N个结点
- 环形链表
- 回文链表
- 有效的括号
- 每日温度
- 柱状图中最大的矩形(选做)
链表题对初学者来说往往不是算法难,而是指针操作细节容易乱。第三天特意把链表和栈放在一起,是为了让你集中处理“递归与迭代”“前驱后继关系”这一类操作思维。
“反转链表”是链表题的基石。这里建议先掌握迭代写法:用prev、curr、next三个指针,每次先保存curr.next,再把curr.next指向prev,然后同步移动。建议你在草稿纸上画一遍流程图,把三指针的移动轨迹画清楚,后续很多复杂链表题的bug都能靠这个图排查出来。
“每日温度”是单调栈的入门题。核心思想是维护一个栈,栈内存的是下标,栈内温度单调递减。当新温度比栈顶元素高时,就说明栈顶元素等到了第一个更暖天气,弹出并计算天数差。这种“延迟匹配”思想非常经典,后面做“柱状图中最大的矩形”时,还是这套单调栈,只是维护的高度顺序不一样。
“有效的括号”是一道效率题,对很多人来说不是不会做,而是代码写得又长又容易漏。其实核心就一句话:遇到左括号入栈,遇到右括号检查栈顶是否匹配。结束时要检查栈是否为空。代码量一般在二十行以内,如果你一写就上百行,大概率是把匹配逻辑写复杂了。
2.4 Day4 二叉树:递归模板暴露你对函数调用的理解
核心模板:遍历框架、递归返回值、全局状态维护
Day4 题组:
- 二叉树的中序遍历
- 二叉树的最大深度
- 翻转二叉树
- 二叉树的层序遍历
- 验证二叉搜索树
- 二叉树的最近公共祖先
- 二叉树的直径
- 从前序与中序遍历序列构造二叉树(选做)
二叉树的核心动作只有一个:遍历。所有树相关题目,都是“在遍历过程中做点事情”。所以第四天也从遍历开始,先分别用递归和迭代实现一下前序、中序、后序这三种遍历,别嫌这基础,后面每一道题几乎都建立在遍历之上。
“验证二叉搜索树”适合用来纠正一个常见错误——只判断左结点小于根结点、右结点大于根结点。这样会漏掉“右子树的左子树里有比根结点更小的值”这种非法情况。正确做法是给递归函数传递一个值范围区间,每个结点都严格落在这个区间内才行。我第一次刷这道题的时候就踩了这个典型的坑。
“二叉树的最近公共祖先”考验的是递归返回值的运用。可以这样理解:在某棵子树里找目标结点,找到了就把结点往上层传,传的过程中如果某一层的左右子树各自都找到了一个目标,那这个结点就是最近公共祖先。这个思路抽象程度比较高,建议结合简单案例画一下递归栈的调用过程,再去看代码,理解效率会高很多。
2.5 Day5 图论与回溯:DFS/BFS状态管理是精髓
核心模板:DFS递归、BFS队列、访问标记与恢复现场
Day5 题组:
- 岛屿数量
- 腐烂的橘子
- 课程表
- 单词搜索
- 全排列
- 子集
- 括号生成
- 解数独(选做)
第五天的内容系统性最强,因为图论和回溯都是“在一张搜索树上做状态管理”的问题。
“岛屿数量”考察的是网格上的DFS。遍历整个网格,每遇到一个陆地格子,就把和它相连的所有陆地都标记成“已访问”,岛屿数量就加一。这里的核心是四个方向的递归搜索和访问标记,不做标记就会死循环。
“全排列”和“子集”是回溯思想的入门题。回溯的精髓是“做选择、递归、撤销选择”。以全排列为例:每次选一个还没用过的数字加入当前路径,递归进去之后,再从路径中移除这个数字。这个“撤销”的动作经常被初学者忽略,于是最终结果会出现大量重复排列。
“课程表”是图论中拓扑排序的典型代表。它其实就是判断课程依赖关系中有没有环。把每门课程看成一个结点,先修关系看成有向边,用BFS方式做拓扑排序:如果最终能处理的结点数等于总课程数,就说明不存在环。这道题也是面试里出现频率极高的一道图论题。
2.6 Day6 动态规划入门:先学会定义状态,再谈转移方程
核心模板:dp数组定义、初始化状态、递推方向选择
Day6 题组:
- 爬楼梯
- 打家劫舍
- 最大子数组和
- 最长回文子串
- 最长有效括号
- 不同路径
- 单词拆分(选做)
第六天开始进入动态规划。很多人对动态规划有畏惧感,其实可以把动态规划看作一种“有记忆的递归”。第六天选的全是线性动态规划和二维路径类问题,它们的状态定义和转移方程都比较直观,适合建立信心。
“爬楼梯”是动态规划最简单的自顶向下或自底向上案例。设dp[i]表示爬到第i阶有多少种方法,那dp[i] = dp[i-1] + dp[i-2],因为最后一步要么走一阶,要么走两阶。这个题能让你建立动态规划最基础的思考框架。
“打家劫舍”稍微复杂一点,关键是理解“选当前房子”和“不选当前房子”之间的转移关系:dp[i] = max(dp[i-1], dp[i-2] + nums[i])。这道题的变体很多,但核心都是这个取与不取的结构。
“最长回文子串”则是区间动态规划的经典题。用二维dp[i][j]表示从i到j这一段是否为回文串。转移的时候要关注扩散顺序:先处理短区间,再处理长区间,因为长区间的判断依赖短区间的结果。很多人会忽略这个递推顺序,导致结果错得莫名其妙。
2.7 Day7 动态规划进阶与综合实战:把前六天的模板串成一条链
核心模板:背包模型、状态压缩、二分查找与动态规划结合
Day7 题组:
- 完全平方数
- 零钱兑换
- 最长递增子序列
- 最长公共子序列
- 编辑距离
- 寻找两个正序数组的中位数(选做)
- 买卖股票的最佳时机
- 乘积最大子数组
最后一天集中处理难度较高的动态规划进阶题,以及几道综合运用前面多天模板的题目。
“零钱兑换”是经典的完全背包问题。dp[i]表示凑出金额i所需的最少硬币数,每个硬币可以重复使用,所以内层循环要按正序遍历。这个题如果能独立写对,说明你已经理解背包问题的两种遍历顺序。
“最长递增子序列”是一道标准的“一维动态规划 + 二分优化”题。设dp[i]表示以nums[i]结尾的最长递增子序列长度,转移时往前找所有比它小的元素即可。如果数据量变大,还可以用维护单调序列的方式把时间复杂度优化到O(n log n)。
“寻找两个正序数组的中位数”放在最后一天,是因为它综合了二分查找、数组划分和边界处理等多种技巧,复杂度较高。这道题不建议死磕太久,我的想法是,如果在30分钟内没有完整思路,就直接看题解,看懂后自己重写一遍,把它当成综合性练习而不是考核目标。
注意:第七天的“买卖股票的最佳时机”看起来很简单,但它其实是线性状态机的入门题。你可以用
hold和cash两个状态变量来模拟持有和空仓时的最大收益,这种状态机思想在更高难度的股票题里会反复出现。
3. 核心解题模板与思路注释
7天看下来,你会发现一个事实:Hot100看似一百个独立题,其实真正用到的解题框架不超过十套。下面这四套框架,是我建议你在7天刷题过程中反复归档和回顾的“母题模板”,每天结束前把当天题归入对应模板,才能避免刷完就忘。
3.1 哈希表模板:空间换时间的核心框架
在需要对数组元素进行“快速查找配对”的场景下,用哈希表把元素值和下标缓存起来,可以把查找时间拉平到常数。
def two_sum(nums, target): seen = {} for i, num in enumerate(nums): need = target - num if need in seen: return [seen[need], i] seen[num] = i return []适用题目不限于:
- 两数之和
- 字母异位词分组(用排序后的字符串作哈希键)
- 最长连续序列(先用哈希集合去重,再统计连续长度)
3.2 双指针框架:一个框架吃透三类问题
双指针看似花样多,其实可以归成三套:
左右对撞指针:指针分别从数组两端出发,向中间移动,常用于有序数组或需要围成区间的问题,例如“盛最多水的容器”和“三数之和”。
left, right = 0, len(arr) - 1 while left < right: if condition(left, right): # 更新答案或做某种操作 left += 1 else: right -= 1快慢指针:一个指针速度快、一个速度慢,常用于链表求环、找链表中点。例如“环形链表”和“删除链表的倒数第N个结点”。
滑动窗口:左右指针同向移动,用一个窗口覆盖一个连续子串,常用于子串和子数组问题。窗口是否需要收缩,由当前窗口是否满足某个条件决定。
提示:遇到子串或者子数组的最值问题,先想滑动窗口;遇到从左右两端选出最优组合的问题,先想对撞指针。这个优先级判断可以帮你少走很多弯路。
3.3 DFS/BFS框架:树和图通用的搜索骨架
树的遍历本质是DFS。图的遍历、岛屿类问题,本质上也是DFS,只是多了一个“访问标记”的动作,防止重复探索。
def dfs(grid, i, j, visited): if outside(grid, i, j) or grid[i][j] == '0' or visited[i][j]: return visited[i][j] = True dfs(grid, i + 1, j, visited) dfs(grid, i - 1, j, visited) dfs(grid, i, j + 1, visited) dfs(grid, i, j - 1, visited)回溯则是在DFS的递归前后多加一个状态的保存与撤销:
def backtrack(path, choices, used): if end_condition(path): result.append(path[:]) return for choice in choices: if used[choice]: continue used[choice] = True path.append(choice) backtrack(path, choices, used) used[choice] = False path.pop()“撤销”这一步是整个回溯算法的灵魂。忘了它,结果里就会充满各种重复分支。
3.4 动态规划五步法:怎么写转移方程不跑偏
动态规划题不需要灵光一闪,它有固定的分析流程。我刷题时严格遵守这五步,强烈建议你也试一遍:
- 确定
dp[i]或dp[i][j]的定义,这一步一定要明确到“下标代表什么状态,存储什么值”。 - 找转移方程,想清楚当前状态是从哪些更小的状态推导来的。
- 初始化边界状态,例如
dp[0]、dp[i][0]、dp[0][j]这些“不能再转移”的状态。 - 确定遍历顺序,是自底向上还是自顶向下,是正序还是倒序。
- 举例带入验证,拿小数据集手动走一遍循环,和代码输出对一下。
以“编辑距离”为例:dp[i][j]表示把word1前i个字符转换成word2前j个字符需要的最少操作数。如果第i个字符和第j个字符相同,dp[i][j] = dp[i-1][j-1];否则取插入、删除、替换三种操作的最小值加一。这个定义清晰后,代码写起来非常顺畅。
4. 7天实战中的常见问题与避坑指南
这七天高密度刷题,一定会遇到一些共性问题。这些坑我基本都踩过,整理出来当避坑指南看,能帮你省下大量时间。
4.1 为什么看了题解我能懂,自己写还是不会
这是7天计划里最普遍的挫败来源。原因是“看懂”和“能独立写出来”之间隔着一道巨大的鸿沟。看题解时,你的大脑处于被动接收信息的状态,而独立写题需要主动调取模板并组合步骤。
应对方法很简单:看完题解后,先关上题解页面,自己从头写一遍。写不出来也别急着再打开,先画出递归或者循环的流程图,用一句人话描述每一步在做什么。比如“这一步是从栈里取出未处理的结点并把它的右子结点压栈”,能说出来才能写出来。
4.2 每天任务量太大,玩不完成怎么办
7天每天15题的量,说实话对在职党或者课业重的学生来说确实偏紧。我做计划时就预留了缓冲空间,每天的最前面几道题是热身题,最后几道是选做或压轴题。如果时间不够,选做题和压轴题可以只看题解、理解思路即可,不用写出完整代码。
也就是说,硬性任务是每天前十题,软性任务是后五题。这样即使某天状态不好,也不至于让计划全线崩盘。
4.3 遇到一道题卡了半小时以上,要死磕还是放弃
死磕一小时不是勇敢,是浪费。
刷题冲刺阶段的时间成本极高,我的建议是卡20分钟还毫无头绪就直接看题解。这并不丢人,关键是要把题解看懂、抄写一遍、然后不借助题解再写一遍。第三遍隔两小时再写,直到题解思路内化成自己的模板。7天的节奏不容许你在单个题上打消耗战。
4.4 刷完一遍就全忘了,说明白刷了吗
会忘才正常,遗忘是大脑对信息的正常过滤,不代表白刷。但你可以通过两种方式显著降低遗忘率。
第一种是建立“题单-模板-复杂度”三方映射表。每天刷完题后,把当天题目按照对应的模板归入表格,比如“滑动窗口:无重复字符的最长子串、最小覆盖子串”。第二遍复习时,你就不是一道题一道题地复盘,而是一组模板一组模板地回顾,复习效率高好几倍。
第二种是隔天快速重刷。睡前把当天题目的思路用一句话写下来,第二天做新题前先看一遍昨天的笔记。比如“每日温度:单调递减栈存下标,遇更大值逐一下标算差值”。这些一句话笔记比代码本身更容易激活记忆。
提示:7天完成Hot100只是第一轮,它不是终点。准确目标是完成“见过一遍、理解一遍、模板归档一遍”。之后再按主题走第二轮,重点做错题和当时跳过没写的题,那时你会明显感觉轻松很多。
4.5 复杂度分析要不要每次都写
面试时面试官几乎必问时间复杂度和空间复杂度,所以刷题时一定要养成每道题都分析复杂度的习惯。不是让你背标准答案,而是自己推导一遍自己的写法。如果写出来一个O(n^2)的解法,先别急着提交,想一想哈希表或双指针能不能把复杂度降下来。这个过程比多刷十道题都有价值。
比如“字母异位词分组”,暴解是对每个字符串排序再比较,复杂度是O(n k log k)(k为字符串长度)。用计数法替代排序,能降到O(nk)。这种优化意识需要在刷题过程中反复刻意练习。
七天刷完一百题,听起来像是一个“速成神话”,但真正执行的节奏并非让你一天从早到晚疯狂提交代码,而是用一个严格的时间边界,逼着自己把零散刷题的精力全部集中到最核心的题型模板上。我始终觉得,刷题的目标不是成为题海里的收集者,而是让面试中遇到任何题目时,你都能在十秒内识别出它的模板类别,然后顺着模板一步步写出可运行、可解释的解法。按照这份清单走完,你大概率会发现自己开始有了这种感觉。