LeetCode Hot100刷题笔记:Java算法面试高频题全解析
其实刷LeetCode这件事我一直觉得最大的门槛不是“算法难”而是“坚持难”。Hot100这套题单能在面试圈里经久不衰不是因为题目本身有多炫技而是它精准覆盖了面试中最高频的考点模型——链表操作、二叉树遍历、双指针、滑动窗口、动态规划、BFS/DFS、栈与单调栈几乎每一道都能在真实面试题里找到影子。我自己在准备面试期间把这100题来回刷了两轮第一轮按标签归类刷第二轮直接随机打乱重刷每一轮都会有新的收获。在开始分享具体笔记之前先聊聊我对Hot100这套题单的整体看法和使用策略。这篇文章会持续更新我把自己在刷题过程中的思路拆解、Java实现时踩过的坑、以及零散但高频的语法点全部沉淀在这里希望能给正在刷题的朋友一些参考。1. 刷题前的准备Hot100 的正确打开方式1.1 为什么首选Hot100而不是按题库顺序刷很多人一开始刷LeetCode喜欢从第1题开始按顺序往下做我试过这个方法大概坚持到50题左右就放弃了。原因很简单题库顺序完全是按题目编号排的难度不会线性增长知识点也是东一榔头西一棒槌你刚把链表搞明白下一道题直接跳到动态规划挫败感很强。Hot100这套题单的价值在于它按“高频”筛选了题目而且这些题分布在各类核心知识点上。刷它的正确姿势不是按编号刷而是按数据结构和算法标签分类刷。比如这周只刷链表类下周只刷二叉树类让同一种解题模式在脑子里形成肌肉记忆比每天换花样要有效得多。我在第一轮刷题时给自己定的规则是按照数组、链表、二叉树、回溯、DP、图论等标签分批刷每批5-7天每道题先独立思考30分钟想不出来再看题解绝不直接看答案每道题做完后写一个简单的思路备注哪怕只有两三句话每个周末把本周刷的题重新快速过一遍重点看备注和易错点这样走完一轮大概需要2到3个月节奏是每天1到2道题。不要贪快质量永远优先于数量。1.2 刷题用的核心工具与模板工欲善其事必先利其器。我先说下我的Java刷题环境本地IDE用IntelliJ IDEA配合一个Github仓库管理刷题代码写完后直接粘贴到LeetCode编辑器里提交。这样做的理由是LeetCode自带的编辑器对调试不友好尤其是涉及到链表、树这类需要可视化结构的数据类型本地IDE配合断点调试效率高得多。还有一个小建议建立自己的模板代码库。刷题过程中你会发现很多代码是可以复用的比如二叉树的前序/中序/后序遍历、二分查找的边界模板、滑动窗口的固定框架、背不过的DP状态转移公式等等。我把这些模板单独存成一个Java文件放在项目里像是在建一个自己的算法工具箱。这是一个很大的效率提升点因为面试场景下时间紧迫你不可能现场去推导一个二分查找的边界条件肯定是直接用熟悉得不能再熟悉的模板。在我的模板库里必存的东西包括二分查找的标准模板和“寻找左右边界”变体链表反转的迭代与递归两版二叉树遍历的递归与非递归实现拓扑排序的BFS框架并查集模板快速幂算法前缀和与差分数组模板有了这些基础操作模板面对中期难度的题目时你的大脑CPU主要用来思考“这道题该套哪个模板”而不是纠结“这个模板怎么实现的”。2. 数据结构类题目的Java实现要点与易错点2.1 链表题的迭代思维从反转链表到K个一组翻转链表在Hot100里出现的频率极高因为链表能考察指针操作的熟练度和对引用关系的理解。Java的链表节点定义通常是ListNode包含val和next两个字段刷题时不需要引入新的依赖包直接用题目给定的类即可。我刷链表题最大的体会是链表题的解题核心不是代码量而是把每一步的指针变动在纸上画清楚。以反转链表为例最经典的pre, cur, next三步走很多人在代码里写了十几行但依然出错原因是没想清楚三个指针在每一轮循环结束后的状态。反转链表有个很好用的规律“改指针前先备份后继”因为一旦改变了一个节点的next方向原本的后继节点就找不到了。Hot100里关于链表的题目分散在各个难度区间。简单的如“相交链表”、“环形链表”核心思路都是快慢指针或者双指针。难一点的如“合并K个升序链表”这个题最优雅的做法是优先队列PriorityQueue每次取出最小的那个头结点加入结果再把该节点的后继放回队列中。Java的PriorityQueue默认是小顶堆刚好符合这道题的需求。链表类题目里我特别想说一个细节很多题如果直接遍历会超时需要用到“快慢指针”来优化成O(n)。比如找链表中倒数第K个节点先让快指针走K步然后快慢指针同步走快指针到达尾部时慢指针正好在倒数第K个位置上。这个技巧看着简单但实际写代码时很容易被K的边界条件K为0、K等于链表长度整晕建议在本地多跑几个边界用例。2.2 二叉树遍历的递归与非递归前中后序的模板记忆术二叉树的题目在Hot100里是重头戏不只是因为它涉及递归思想更因为很多复杂题都是在遍历框架上做文章。递归遍历是最容易写的三步法退出条件、递归左、递归右。但很多面试官会追问“不用递归怎么写”这时候非递归遍历就显得非常重要。我整理了一个记忆非递归遍历的方法前序和层序用栈或队列处理时顺序是相对直观的中序和后序则需要利用栈的迭代替换递归过程中的回溯节点。具体来说中序非递归的模板是不断把左孩子压栈出栈时访问节点然后转向右孩子继续这个过程。后序非递归稍微绕一点最简洁的方式是使用“反转前序”的思路——先按“根右左”的顺序遍历最后整体反转结果。这份整理是不是觉得复杂其实写代码不需要背理解了栈模拟递归的“上下文恢复”逻辑就水到渠成了。我建议想做对二叉树类题目先能手写非递归的前序和中序这是最低门槛。后序可以直接在面试时表示“这里我用递归实现”一般不会扣分。二叉树另一个高频模型是层序遍历BFS用队列实现每轮循环记录当前队列长度size然后弹出size个节点处理。这个模式在“二叉树的右视图”、“二叉树的层序遍历”以及图论里的BFS最短路径中都用得上。我把这个模板固定在了自己的模板库里遇到相关问题直接粘贴改造。2.3 数组与矩阵双指针和前缀和的灵活应用数组类是Hot100里数量最多的题目的“套路化”程度也最高。这里说的双指针不是简单的首尾指针而是一个抽象概念在遍历过程中维护两个“哨兵”位置通过它们的移动缩小问题规模。经典例子是“盛最多水的容器”和“三数之和”。以“三数之和”为例这道题给我留下的教训是去重操作必须在循环内实时做而不是最后靠Set去重。如果你把所有结果存在HashSetListInteger里再去重会面临两个问题一是内存浪费二是List的哈希逻辑基于元素顺序如果两个List元素顺序不同但内容相同哈希值也会不同去重效果就不稳定。正确做法是排序后固定一个数再用双指针找另外两个数同时跳过重复元素。前缀和也是数组题中隐藏的常用技巧。Hot100里的“和为K的子数组”和“区域检索”都用到了前缀和思想。我第一次做“和为K的子数组”时直接用了双重循环时间复杂度O(n²)数据量一大就超时。后来才意识到这种“连续子数组之和比较”的问题本质上是把sum[j] - sum[i]转化为在哈希表中查找sum[i] - k是否出现过。这个题我建议仔细体会它是从暴力到优化的一个典型思维跃迁。3. 高频题型拆解从暴力到最优解3.1 滑动窗口的框架与套路滑动窗口是Hot100中“字符串”和“数组”题型的常客它解决的是一类固定模式的问题“在一个序列中找到满足某种条件的最短/最长连续子数组子串”。这类题如果用暴力枚举时间复杂度很容易到O(n²)但是用滑动窗口可以降到O(n)。滑动窗口的核心是四个变量left、right窗口左右边界、count/map用于记录窗口内元素状态、result暂存最优解。我去刷题时给自己规定了一个固定动作每次移动right扩展窗口检查窗口是否满足条件不满足则移动left收缩窗口直到窗口恢复合法状态。这个“扩展-收缩”的过程非常模式化。Java在这个场景中常用HashMap记录字符出现的次数当题目涉及重复字符时或者直接使用int[256]这样的字符计数数组后者速度更快但可读性稍差。我有次在“无重复字符的最长子串”这道题上WA了几次原因就是我先更新了left再更新了maxLength导致漏掉了一种边界情况。后来我把“先保证窗口合法再计算当前最优解”这个顺序写成了固定步骤每次都先移动窗口、再统计最优值这个错误就再也没有出现过。3.2 动态规划与状态机二维DP的实现细节动态规划是Hot100里最容易让人心态爆炸的题型。不是因为它考察的数学思维有多强而是很多时候你确实想破了脑袋也想不出状态转移方程。这里我给三条可执行的经验第一DP题先不要直接想代码先在纸上定义出状态的含义。比如“编辑距离”这道题状态定义为dp[i][j]表示word1的前i个字符转成word2的前j个字符所需的最少操作数。一旦这个定义清晰了转移方程基本就是照着这个定义去讨论“最后一个字符是否相等”的两种情况。第二二维DP的边界条件极容易出错。我吃过很多亏的地方是二维数组的dp[0][j]和dp[i][0]的初始化。这类“哨兵行/列”的初始值往往不是0而是根据实际业务意义推导出来的比如编辑距离里dp[i][0] i就代表“把一个长度为i的字符串删空需要i次操作”。所以我建议每次写完二维DP后先用小数据跑一遍逐个检查边界行和列的值是否合理。第三滚动数组优化要谨慎用。很多二维DP可以用滚动数组把空间复杂度从O(n²)降到O(n)但这样做会让代码的可读性变差而且如果转移方程里同时依赖i-1行和i行左侧的元素滚动数组的更新顺序就需要注意否则会覆盖旧数据。“最小路径和”和“不同路径”这类可以用一维数组优化但编辑距离这类依赖“左上角”的题滚动数组就需要额外用一个变量保存左上角的旧值。建议先把O(n²)版本的代码跑通再考虑优化。3.3 回溯与剪枝Java集合引用的坑回溯算法是年轻程序员最容易写错又好几天找不出问题的一类题目。核心思想是“尝试-递归-撤销”这个“撤销”动作是绝大多数人的易错点。拿“全排列”来说如果每次递归都新建一个List传入下一层那就不存在“撤销”问题但代价是内存消耗大。更常见的写法是使用一个“路径”List递归完成后做path.remove(path.size()-1)来撤销刚才的选择。这个撤销操作很容易忘一旦忘掉你会发现结果里混入了非常奇怪的重复排列。在Java里还有一个隐藏的坑递归中如果把当前路径直接加入结果集必须做拷贝。我有一次写“子集”这道题直接result.add(path)结果最后result里全是空列表。这是因为path是同一个对象的引用后续的递归修改会直接改动已经存入result中的内容。正确做法是result.add(new ArrayList(path))先拷贝一份当前路径的快照再存入结果集。这个坑我用了一次就记到了死希望看到这篇笔记的你能直接避开。回溯的剪枝也很重要。适合用used数组或boolean[] visited来标记哪些元素已经被选取过如果题目要求结果不重复比如包含重复元素的数组求子集还需要先排序再通过判断i 0 nums[i] nums[i-1] !visited[i-1]来跳过同一层级的重复分支。3.4 BFS与图的遍历队列解最短路径Hot100里的“腐烂的橘子”、“单词接龙”、“岛屿数量”都是BFS的绝佳练习素材。BFS的关键在于“逐层向外扩展”而这一特征用队列实现时每次需要先记录当前队列的大小再一次性消耗完这一批元素这样就能把“层”的概念从队列中显式剥离出来。关于“腐烂的橘子”我看到网上很多讨论都在纠结如何设计visited数组或如何保存坐标。其实这道题有个更简洁的思路先把所有初始腐烂的橘子入队同时统计新鲜橘子的数量。接着BFS扩散每一轮扩散后更新新鲜橘子的数量当队列为空时如果新鲜橘子总数大于0则说明有些橘子永远不会被腐烂返回-1。如果都为0则返回扩散的轮数。BFS在Java中实现时二维坐标的矩阵输入通常用int[]表示即x point[0],y point[1]。方向数组int[][] dirs {{1,0},{-1,0},{0,1},{0,-1}}几乎可以说是所有网格图BFS的标准配置建议直接写熟。4. Java语法与易错点刷题之外的隐形分数4.1 集合框架的选用时机与性能差异Java刷题有个区别于Python/C的特点——集合类的选择对性能影响极大。我最早刷题时喜欢一切皆ArrayListHashSet但后来发现有些场景用LinkedList或ArrayDeque会更合适。这里给出我常用的选型经验场景推荐集合理由频繁头尾增删ArrayDeque双向队列O(1)的头尾操作需要按下标随机访问ArrayList底层数组随机访问O(1)频繁在中间插入/删除LinkedList链表结构不需要搬移元素元素去重且无序HashSet哈希表实现O(1)查找保持插入顺序的去重集合LinkedHashSet底层再维护一条链表需要有序的输出TreeSet红黑树实现按自然序迭代另外还有一个日常刷题中容易忽视的性能点ArrayList初始化时就指定容量。很多人在Hot100里处理较大的测试用例时突然遇到TLE不是算法复杂度不对而是反复扩容导致常数过大。建议提前估算容量比如new ArrayList(nums.length)尤其当你已知结果规模的大概上限时这一行代码能省下不少时间。Java的String相关的题也有大坑。刷字符串类的题如果涉及大量拼接千万不要用String 因为字符串是不可变对象底层每一次拼接都新建一个对象复杂度很可能退化成O(n²)。正确做法是用StringBuilder的append方法。面试场景下你熟练使用StringBuilder这本身也是一个加分项它说明你了解Java字符串的基本机制。4.2 排序、比较器与lambda表达式Java的Arrays.sort()和Collections.sort()是刷题时的必备工具但默认的自然顺序往往不能满足题目要求你需要自定义比较器。Java 8以后最顺手的方式就是lambda表达式例如// 按区间起点升序排序 Arrays.sort(intervals, (a, b) - a[0] - b[0]);这里要特别提醒lambda里直接返回两个数的差值在处理极限数值时可能出现整数溢出问题。比如按分数降序排列(a, b) - b[1] - a[1]如果b[1]是Integer.MAX_VALUEa[1]是负数减法直接溢出了。稳妥写法是使用Integer.compare(b[1], a[1])或者Comparator.comparingInt(...)链式调用。对二维数组按第二列排序、对Listint[]排序这类需求我在Hot100的“合并区间”、“会议室”这类题中反复用到。可以提前把这种自定义排序lambda写法练到闭着眼睛都能写出来现场面试时是非常好的“印象分”。4.3 位运算与特殊进制转换Hot100里有些题目用位运算解法非常优雅比如“只出现一次的数字”利用异或运算的交换律和结合律把成对出现的数字全部抵消掉剩下的就是那个唯一出现一次的数字。用Java写这题只需一个循环异或即可。“基本计算器”这类题虽然主体是栈但解析数字时也常需要用到进制累积技巧遍历字符串中的数字字符时用num num * 10 (ch - 0)逐步把字符转换成真正的数值。这个写法看似简单却经常在“数字后面跟着运算符”的边界处理时出错需要配合判断当前字符是否已经到达表达式的末尾来做收尾操作。关于二进制和位运算Java中,|,^,~,,,这几个运算符含义和优先级与C系语言一致刷题需要用到时留意一下无符号右移和普通右移的区别比如处理负数时保留符号位而不保留。在第一轮刷题时可以刻意挑几道位运算题练手你会慢慢发现二进制直觉的培养对很多题都有帮助。4.4 经典易错点清单实时更新把我在刷题过程中反复踩过的坑汇总成一个清单每次代码提交前我都会快速过一遍这些点真的能拦截掉不少WA数组越界循环写i nums.length的经典手误二维数组访问时的行列搞混空值处理链表题不判断head为null二叉树题不判断root为null字符串题不处理空串负数取模Java%的符号与左操作数一致-5 % 3结果是-2而不是1部分数学题会遇到Integer缓存“Integer比较用”这个误区面试八股常考刷题时如果要用HashMap做计数键永远用基本类型包装类而要谨慎比较栈的peek和pop混淆peek()只看不取pop()取走并删除很多人写BFS/DFS的代码时一时手滑递归的返回值类型不匹配有的递归函数应该有返回值你却把它设计成了void导致父层拿不到子层的结果字符与数字互转0的ASCII码是48用ch - 0而不是Integer.parseInt(ch )String不可变性字符串做内层循环修改时还是用char[]或者先转为StringBuilderList去重的哈希冲突前面提到过的add(new ArrayList(path))切忌直接加入引用Comparator返回值为0时的稳定性排序结果可能不稳定如果题目要求多关键字排序要用thenComparing链式指定这些都写在了我的笔记置顶位置刷题两轮之后对所有问题点基本都有印象了。5. 两道Hot100经典题的完整手撕过程5.1 “爱吃香蕉的狒狒”二分查找里的边界细节“爱吃香蕉的狒狒”这道题在热搜里被很多人关注是二分查找在“最小化最大值”场景下的经典应用。题目核心是给定一堆香蕉堆和总时间H求最小速度K使得在H小时内吃完所有香蕉。每堆香蕉如果你用速度K去吃如果数量小于K也要花1小时整。这个问题的关键词是“最小速度K”并且满足单调性速度越大吃完所需的总时间越短。这种“某种条件随参数增加而单调变化”的场景是非常典型的二分查找信号。我第一次做这题时用了二分模板的“左闭右闭”形式但卡在了两个点上第一个是上界选多少。一开始我直接选了max(piles)作为上界后来发现在某些特殊测试用例中可能不够因为虽然有香蕉堆的数量上限但总时间H可以很小这时K可能需要大于最大堆的香蕉数吗仔细分析如果K已经大于最大堆的香蕉数那么每堆香蕉都只花1小时吃完总时间等于堆数。若H大于堆数那K确实不必超过最大堆但若H小于堆数则没有解题目也保证有解存在。因此上界设置成max(piles)是安全的但从工程角度我一般习惯取max(piles)因为这已经是真实边界。第二个是时间计算函数。对每堆香蕉pile吃得时间(pile K - 1) / K这个向上取整的写法就是“除以K并向上取整”的经典表达式。我第一次用Math.ceil((double)pile / K)来处理结果在小数据上没问题但数据量变大时浮点数精度会产生偏差提交偶尔WA。换成整数运算(pile K - 1) / K之后问题彻底消失。这也是一个小经验刷题时能用整数运算替代浮点就直接用整数运算浮点的不精确性本来就是算法题里的一个坑。贴一下核心代码Java版public int minEatingSpeed(int[] piles, int h) { int left 1, right 0; for (int p : piles) { right Math.max(right, p); } while (left right) { int mid left (right - left) / 2; if (canFinish(piles, mid, h)) { right mid; // 尝试更小的速度 } else { left mid 1; } } return left; } private boolean canFinish(int[] piles, int speed, int h) { int time 0; for (int p : piles) { time (p speed - 1) / speed; if (time h) return false; // 提前退出避免int溢出 } return time h; }注意这里canFinish提前退出的判断很重要不仅能加快速度还能防止time累加溢出。left (right - left) / 2的写法是为了防止两个大整数相加时的溢出问题也是面试官比较喜欢看到的细节。5.2 “腐烂的橘子”的BFS轮数计算技巧这题我在开头提过基本思路这里完整展开说一下。给定一个m x n的网格0代表空单元格1代表新鲜橘子2代表腐烂橘子。每分钟腐烂的橘子会污染上下左右相邻的新鲜橘子问多少分钟后网格中所有橘子都腐烂或者返回 -1。这题最容易出错的不是BFS本身而是轮数分钟数的统计方式。如果你在每层出队时记录steps那你要很小心初始状态的处理当初始腐烂橘子已经存在但还需要1分钟才能感染邻居时你是要用steps还是steps-1这里很容易多算一分钟或者少算一分钟。推荐一个干净的思路BFS开始前把所有的腐烂橘子入队记录fresh的数量。每一轮循环中先把当前队列的size固定下来然后只处理size个节点这就是“这一分钟内会发生的腐烂传播”。处理完后minutes。当队列为空时检查fresh是否为0如果不为0说明有孤岛橘子返回 -1否则返回minutes。这个写法的好处是“层数”和“时间”天然对应代码不容易出错。核心代码如下public int orangesRotting(int[][] grid) { int m grid.length, n grid[0].length; Queueint[] queue new LinkedList(); int fresh 0; for (int i 0; i m; i) { for (int j 0; j n; j) { if (grid[i][j] 2) { queue.offer(new int[]{i, j}); } else if (grid[i][j] 1) { fresh; } } } if (fresh 0) return 0; int[][] dirs {{1,0},{-1,0},{0,1},{0,-1}}; int minutes 0; while (!queue.isEmpty()) { int size queue.size(); boolean changed false; for (int i 0; i size; i) { int[] cur queue.poll(); for (int[] d : dirs) { int nx cur[0] d[0]; int ny cur[1] d[1]; if (nx 0 || nx m || ny 0 || ny n || grid[nx][ny] ! 1) { continue; } grid[nx][ny] 2; fresh--; queue.offer(new int[]{nx, ny}); changed true; } } if (changed) minutes; } return fresh 0 ? minutes : -1; }这里的changed布尔变量是可选的它的作用是防止最后一轮BFS没有实际感染却依然增加分钟数。如果你用“分层BFSwhile队列非空”的结构有的参考写法里会在每次外层循环结束直接minutes这时候最后一轮队列为空的情况会多算1分钟所以我更推荐加一个changed判断。BFS还有一个前置注意点入队时就标记grid[nx][ny] 2而不是出队时再标记。否则会发生同一个新鲜橘子被多个腐烂橘子重复入队的情况导致结果偏大或者死循环。这一点在“岛屿数量”这类题中也同样适用标记访问的动作必须放在入队时。6. 刷题节奏与面试准备心得6.1 刷完Hot100一轮后如何自测一轮刷完不等于万事大吉。我自己在一轮结束后做的第一件事是把所有题目标题列成一个表格标出每道题当时的AC时间、WA次数、关键词标签。然后随机抽出10道曾经WA过的题不看自己的备注直接重写。这个自测方式非常有效比按顺序重刷更能检验真实的记忆水平因为随机抽题切断了“因为上一题是XX类型所以这题大概是XX类型”的惯性联想。如果随机抽出的题能在30分钟内AC且没有查看参考代码说明基本掌握了。如果卡住了就把这道题标记为“二轮重点题”几天内再刷一遍。这样循环两轮之后Hot100的核心题基本都能形成稳定的解题反射。我也建议大家保留每个版本的提交记录。LeetCode的提交历史能回看每一次WA和AC的代码差异这是非常好的复盘素材。我有次发现自己的某个题WA了5次翻看提交记录才意识到每次都是同一个边界没处理好于是把这个边界抽象成了模板思想写进笔记。复盘的深度决定了刷题的收获上限。6.2 结合八股文与Java面试准备刷Hot100和准备Java面试可以并行进行两者并不冲突而且互相成就。热搜词里反复出现的“Java八股文”和“Java基础面试题”本质上和算法题在面试考核中占据的权重是并行的。面试中算法题做得好极大缓解了后续八股问答的紧张感。我的做法是每天前半段时间刷算法题保持手感后半段复习Java基础集合源码、并发、JVM、Spring等。特别是刷题过程中频繁用到的HashMap、PriorityQueue、ArrayDeque这些类我会顺手去翻一下源码理解底层是数组还是链表、扩容机制是什么。这样做的好处是“以用带学”你对这些集合的理解能落在地上回答“HashMap底层原理”这类八股题时视角会比较实际而不是背诵。比如很多人在面试中回答“HashMap的put流程”时如果刷题时经常用size、getOrDefault这些API你会在理解层面自然更倾向于用“键值对存储和查找”的视角去分析而不是反复背那几个步骤。这种结合让我在面试时对于“算法实现里为什么选用这个集合”这类追问非常从容因为确实是自己天天在用的东西。6.3 给新手的刷题避坑建议这一小节的内容想给刚开始刷题的朋友几条实用建议不浇鸡汤直接说可执行的方向第一不要在一道题上死磕太久。独立思考30分钟是一个标准的上限。超过这个时间如果依然没有任何思路直接看题解并理解其思路再合上题解自己重新写一遍。这个过程比死磕一道题一天的价值高得多。所谓“刷题”的目的不是证明自己能独立做出来而是通过反复训练建立解题直觉。第二一定要手动跑小样列子。哪怕你已经觉得代码逻辑天衣无缝了提交前也应该在本地用一个非常小的输入比如3个节点、4个元素的数组手动模拟一遍。算法题里大多数WA都发生在边界条件上而小样本输入是发现边界问题最快的方式。第三建立错题重刷周期。我用的是一个很简单的办法每周末随机抽5道本周做过的题每道题给20分钟不AC就下周继续抽。这种做法让错题不会变成“做过就忘”而是真正地被内化。第四重视代码风格和命名。虽然算法题核心是思路但面试过程中面试官会直接看你现场写的代码。变量命名清晰比如left、right、slow、fast、cur、prev、逻辑结构紧凑、关键步骤有注释这些习惯需要平时练真到面试现场你很难临时改变书写习惯。我的经验是把代码当成写给同事Review的作品来写思路不只是让自己看懂还要让阅卷者看懂。7. 持续更新的维护机制我会持续把Hot100中更多题目的思路和易错点补充进这篇笔记形成一个真正能随刷随查的更新型文档。为了维护方便我的实际做法是每次用LeetCode的Cookie下载自己的提交记录导出一个JSON文件配合脚本统计哪些题目WA次数最多、哪些题的AC时间最长然后优先把这些题目整理成笔记。我用了一个简单的维护原则不只是记录“这道题怎么做”而是记录“这道题让我明白了什么”。比如做“基本计算器”时我学到的核心是“用栈保存符号状态延迟计算”做“柱状图中最大的矩形”时学到“单调栈的本质是寻找左右第一个比当前元素小/大的位置”。当每道题都能浓缩为一两句话的核心启发时这份笔记就会慢慢变成一本自己专属的算法思维词典价值远大于只收藏别人的题解。同时我也会把Hot100之外但和这些题目同类型的高频题比如周赛中的新题、大厂面试中出现过的变体顺手补充进来当作练习延伸。保持这个笔记和题单同步更新让每一次打开它都有新鲜的参考内容。最后分享一个小习惯我会把Hot100的题单导到备忘录里每完成一道题就在题目旁边标记当天日期和WA次数。三个月后回头看这个列表上面记录的不是简单的对号而是一条清晰的从“易错”到“熟练”的曲线这种进步感本身就是支撑我持续刷下去的最大动力。希望这篇笔记也能成为你的刷题路程上的陪伴文档欢迎随时回来查阅更新。