
简介这是一份面向算法面试与LeetCode刷题者的PDF资料聚焦热题100中的典型题目提供C解答与解题思路。内容从两数之和的哈希表优化、两数相加的链表进位处理到无重复字符最长子串的滑动窗口法均给出可运行的代码与复杂度分析适合正在准备技术笔试、希望提升编码效率的开发者使用。资源包包含1个PDF文件整体大小约640KB轻量易查阅。目前已有1652人学习/下载。除题目与答案外资料还整理了算法解题框架与LeetCode平台使用技巧帮助读者掌握从读懂题意到代码实现的完整流程可作为刷题笔记或面试前快速回顾的参考资料。1. 别再盲目刷题了LeetCode热题100到底在解决什么问题如果你正处在“刷了三百题面试还是被一道中等题卡住”的状态问题多半不在题量而在你刷的是不是LeetCode热题100。这套题目加答案的组合不是让你少刷题而是把有限时间花在最高频的考点上。作为一线工程师我用热题100带过好几个转行的同事也用它自查知识盲区。最大感受是它把“算法玄学”变成了“按场景调用模板”每个标签下都是面试官真正爱考的题型答案代码则像一个精确的裁判让你立刻看到差距。这篇文章会拆清这100题怎么选、怎么刷、答案怎么读以及那些容易翻车的边界条件和复杂度陷阱。适合准备算法面试但时间有限的开发者也适合想系统查漏补缺的熟手。2. 热题100的选题逻辑先弄清这100题覆盖了哪些核心考点拿到热题100先别急着从头开始刷。很多人刷到第20题就卡住又回去翻前面反复横跳。我的习惯是先按标签把它过一遍心里有个地图再定刷题顺序。这一章就讲这个地图长什么样。2.1 按数据结构和算法维度拆解热题100的分布虽然名单每年变动但核心分布非常稳定。我按常见标签统计过大致是这样的类别高频题型举例核心考点数组/字符串两数之和、无重复字符的最长子串、合并区间哈希表、双指针、排序链表反转链表、环形链表、合并两个有序链表虚拟头节点、快慢指针、递归树二叉树中序遍历、层序遍历、验证二叉搜索树递归、DFS/BFS、中序遍历性质动态规划爬楼梯、打家劫舍、最长递增子序列状态定义、转移方程、空间压缩滑动窗口/双指针最小覆盖子串、盛最多水的容器、三数之和窗口边界、单调性、排序去重其他高频有效的括号、岛屿数量、课程表栈模拟、DFS连通性、拓扑排序这张表不是官方分类是我自己按题目特征整理的但足够反映热题100的覆盖思路它不是一个算法竞赛题库而是一个面试高频场景库。为什么数组/字符串占最大头因为数组本身承载了哈希、双指针、二分、排序、区间合并这些最常用技巧。字符串题往往是数组题的变形换了一层皮而已。链表题数量不多但每个面试官都爱从链表开始它能快速判断你对指针操作和引用赋值是否敏感。树题考的是递归递归又是动态规划和回溯的地基所以树的优先级并不比链表低。动态规划是区分度最大的部分。热题100里的DP题大多属于一维DP或简单二维DP比如爬楼梯、打家劫舍、最长递增子序列。这些题不会考状态压缩的奇技淫巧但会考你能不能快速定义一个合理的dp数组并写出转移方程。滑动窗口和双指针则是现场手写最容易乱的部分窗口左右边界的移动条件稍不留神就写错。了解了这些分布你就能明白热题100不是让你做偏怪题而是用最经典的题把高频考点全部占住。这也正是“题目加答案”组合的价值——当你拿到一份整理好的答案时你能清晰看到这道题所属类别而不是只看到一道孤立的题。2.2 高频考点的优先级排序先刷哪些、后刷哪些我一般把热题100的考点分成三个梯队。第一梯队是数组哈希、链表、二叉树这三类题独立性强刷完能建立信心也是后面很多难题的构件。第二梯队是动态规划、滑动窗口、回溯这些是面试区分度的关键适合在掌握基础后集中突破。第三梯队是堆、图、拓扑排序数量少但容易被突击抽查。如果你只有两周准备时间我建议按“数组→链表→树→动态规划→滑动窗口”的顺序刷。不要按题号从1刷到100那样知识点是跳跃的效率很低。LeetCode支持按标签筛选先勾选数组、链表、树把简单和中等题刷完再切到动态规划和滑动窗口。前两周每天5到6题第一遍只做简单和中等级别困难题先看答案但要把思路用自己的话写在题目旁边。这里要强调答案不是用来抄的而是用来对答案的。每道题独立写上20分钟卡住了再看答案的第一段思路说明然后合上答案自己写。写不出来再打开看代码把代码理解之后立刻统计自己卡在哪一步是状态定义没想清楚还是边界漏了。这个过程比单纯刷题慢但一轮下来比无脑刷3遍更扎实。优先级排序的另一个理由是依赖关系。比如很多滑动窗口题其实用了双指针思想而双指针又经常出现在有序数组上排序算法在数组题里反复出现。先把数组题刷熟再刷滑动窗口就会顺很多。树题则是递归的地基递归没想明白动态规划的状态转移也很难讲清楚。所以“先基础、再抽象”不是教条而是顺着知识依赖走的经验。3. 用“题目答案”刷题的正确姿势从读题到复盘的一整套流程热题100的题目和答案都在手边怎么用才不浪费我总结了一套流程读题、抽象、暴力解、优化、写码、复盘。下面用“两数之和”这道入门题完整演示一遍。3.1 拆解一道热题以两数之和为例的完整分析题目描述是给定一个整数数组 nums 和一个整数目标值 target找出和为目标值的那两个整数返回它们的数组下标。假设每种输入只对应一个答案并且不能重复使用同一个元素。第一眼很容易想到暴力解两层循环检查每一对元素。时间复杂度 O(n^2)空间复杂度 O(1)。这种解法没问题但面试官一定会问“能不能更快”。更优的做法是用哈希表把已经遍历过的数存起来每到一个新数就查它的补数是否在哈希表里。我常用的写法是def two_sum(nums, target): seen {} # key是数字value是下标 for i, num in enumerate(nums): complement target - num # 找补数 if complement in seen: return [seen[complement], i] # 找到配对 seen[num] i # 没找到则存入当前值 return []逻辑说明seen 是一个字典key 是遍历过的数字value 是它第一次出现的下标。每处理一个新元素 num先计算 complement target - num然后检查 complement 是否在 seen 中。如果在说明之前出现过可以和当前元素配对的数直接返回两个下标。如果不在就把当前 num 和下标 i 存进 seen继续遍历。这里有一个容易被忽略的细节先查补数再存当前值这样就能保证不会把同一个元素用两次。比如 nums[2,7,11,15], target9遍历到 2 时补数 7 不在把 2 存进去遍历到 7 时补数 2 在返回下标 [0,1]。参数说明时间复杂度 O(n)因为每个元素最多被哈希表查询和插入各一次空间复杂度 O(n)因为最坏情况下需要存储 n-1 个元素。如果找不到答案返回空列表而不是抛异常这是和题目约定“假设有一种答案”保持一致的写法。3.2 读懂答案代码怎么从“能跑”看到“更优”很多人看答案只看代码是否通过了用例这远远不够。我一般会看三个点状态设计、循环不变式、边界处理。以两数之和为例答案为什么用补数作为 key而不是用其他东西因为目标值固定一旦确定一个数另一个数就被唯一决定了。这个“补数”思想可以迁移到很多场景比如判断两个字符串是否是字母异位词时用计数器做抵消运算。循环不变式则是在遍历到第 i 个元素时前 i-1 个元素都已经存入哈希表因此不会漏解。边界处理则包括数组为空、长度为 1、所有数都不满足条件时怎么办。读答案时我强烈建议只看思路提示不要直接看完整代码。我一般会把一份好答案拆成“思路提示”和“代码实现”两层先只读前者合上屏幕自己写。写出一个可运行的版本后再打开答案对照差异。这样你才能知道自己和标准思路之间的gap在哪儿。否则你背下来的只是一段代码而不是一种可迁移的算法思维。3.3 刷题复盘模板三句话记录法每道题提交通过后我会在笔记里写三句话用来做即时复盘。第一句这道题的核心数据结构是什么是哈希表、栈、还是快慢指针。第二句最优解比我的解好在哪是减少了一层循环还是用滚动变量省掉了数组。第三句变式威胁如果输入有序怎么做如果要求输出所有组合怎么改如果有负数怎么处理。以两数之和为例我的记录是核心数据结构是哈希表最优解用空间换时间把 O(n^2) 降到了 O(n)变式是数组有序时可以用双指针空间降成 O(1)但双指针要求数组已排序所以“有序”是关键前提。这道题的扩展“三数之和”就要先排序然后用固定一个数加双指针的方式复杂度 O(n^2)。把这个三句话记完这道题才算真正留在脑子里。这套流程看似慢但每道题只需额外花三到五分钟。热题100一共100题总复习成本并不高效果却比“刷一遍加忘一遍”好得多。4. 三类必考题型的解题模板链表、动态规划、滑动窗口热题100里有很多题属于同一题型我总结了三个最常出现的模板链表类、动态规划类、滑动窗口类。模板不是让你死背而是给你一个默认的触发条件看到什么信号就先往哪个方向想。4.1 链表类虚拟头节点和双指针的套路链表题最怕指针乱飞。我一般先判断这道题是不是要修改链表结构如果是优先考虑“虚拟头节点”和“双指针”两个工具。以反转链表为例题目很简单但能考察你对引用的理解。def reverse_list(head): prev None # 反转后的前驱初始为空 cur head while cur: next_node cur.next # 先保存下一个节点防止丢链 cur.next prev # 当前节点指向前驱 prev cur # 前驱前移 cur next_node # 当前节点继续往前走 return prev # 新头节点逻辑说明核心动作是“保存下一个节点再把当前节点指向前驱”。如果不提前保存 next_node改完 cur.next 之后就找不到原来的下一个节点了。prev 最终指向反转后的新头节点。参数说明时间复杂度 O(n)空间复杂度 O(1)没有使用额外的链表存储。再看一个更实际的操作删除链表倒数第 N 个节点。如果从零开始找倒数位置很容易漏掉“删除头节点”这种边界。常见的做法是引入 dummy 虚拟头节点让 fast 指针先走 N1 步然后 fast 和 slow 一起走当 fast 为 null 时slow 正好指向待删除节点的前驱。def remove_nth_from_end(head, n): dummy ListNode(0) # 虚拟头节点统一边界处理 dummy.next head first dummy second dummy for _ in range(n 1): first first.next # 快指针先走 n1 步 while first: first first.next second second.next # 慢指针同步移动 second.next second.next.next # 跳过待删除节点 return dummy.next # 返回真实头节点逻辑说明dummy.next 指向 head哪怕删掉头节点也能通过 dummy.next 返回正确的链表。first 先走 n1 步然后 first 和 second 同步移动。当 first 走到链表末尾时second 正好停在待删除节点的前一个节点执行 second.next second.next.next 即完成删除。参数说明时间 O(n)空间 O(1)。这个解法同样适用于“找链表中间节点”和“判断链表是否有环”区别只是快指针的步长不同。4.2 动态规划类状态定义和转移方程的推导模板动态规划题在热题100里占有很高权重但很多人把它当成玄学。我一般用“爬楼梯”作为入门题来演示三要素状态定义、转移方程、初始化。def climb_stairs(n): if n 2: return n prev2, prev1 1, 2 # dp[1], dp[2] for i in range(3, n 1): cur prev1 prev2 # dp[i] dp[i-1] dp[i-2] prev2 prev1 prev1 cur return prev1逻辑说明dp[i] 表示爬到第 i 阶的方法数。到达第 i 阶只可能从第 i-1 阶迈一步或者从第 i-2 阶迈两步所以 dp[i]dp[i-1]dp[i-2]。初始化 dp[1]1、dp[2]2。代码里用 prev2 和 prev1 代替数组因为每个状态只依赖前两个状态滚动变量可以把空间从 O(n) 降到 O(1)。参数说明时间 O(n)空间 O(1)。做动态规划题时我会按四个步骤走。第一步明确 dp[i] 的含义这句话必须能回答清楚“下标代表什么、值代表什么”。第二步找转移关系先在纸上列几个小例子验证。第三步处理边界通常看 i0、1、2 的情况。第四步想能不能用滚动变量压缩空间。热题100里的打家劫舍、最长递增子序列都可以套这个流程区别只在转移方程里多加了一个比较逻辑。一个常见的错误是跳过第一步直接套斐波那契公式。比如打家劫舍是 dp[i] max(dp[i-1], dp[i-2] nums[i])和爬楼梯的加法完全不同。如果你没想清楚 dp[i] 的意义写出来的方程就是错拼而答案代码的作用正是帮你对照看作者的 dp[i] 代表什么看初始化为什么是这些值然后再自己写一遍。4.3 滑动窗口类同向双指针的模板代码滑动窗口题在笔试和面试里出现频率很高我把它当作纯模板题来准备。先记住一个默认框架右指针负责扩展窗口左指针负责收缩窗口循环体内先更新右边界再在满足条件时收缩左边界。以“无重复字符的最长子串”为例def length_of_longest_substring(s): char_set set() # 记录窗口内出现过的字符 left 0 max_len 0 for right, ch in enumerate(s): while ch in char_set: # 窗口内已有重复字符 char_set.remove(s[left]) left 1 # 左指针不断右移直到不重复 char_set.add(ch) max_len max(max_len, right - left 1) return max_len逻辑说明char_set 维护当前窗口里出现过的字符。每遇到一个新字符先检查窗口里有没有重复有重复就持续移除最左边的字符并把 left 右移直到窗口不再包含当前字符。然后把当前字符加入窗口更新 max_len 为窗口长度。参数说明每个字符最多被加入和移除一次时间复杂度 O(n)空间复杂度 O(k)k 是字符集大小比如 ASCII 字符集的话就是常数。这个模板可以直接迁移到“最小覆盖子串”只需做两处改动一是把 set 改成计数器 dict用来记录窗口中某个字符还需要多少个二是右指针扩展后先用一个计数变量判断窗口是否已经完全覆盖 t覆盖后再收缩左边界并在每次覆盖时更新最小长度。这两类题表面不同但骨架都是“右进左出”所以我把它们归到同一个模板下面试时能少想很多无关细节。不过滑动窗口最容易出错的是收缩条件。无重复字符的收缩条件是“窗口中出现重复字符”而最小覆盖子串的收缩条件是“当前窗口已经包含所有目标字符”。如果你把这两个条件混着用写出来的代码要么漏解要么死循环。这也是为什么答案代码值得精读不是背代码而是背“条件”和“边界”。5. 刷热题100的避坑指南五条血泪经验这一部分来自我带人刷题时的血泪经验全是反复出现过的坑。每一条我都按“现象、原因、解决”来写希望能帮你省掉几个晚上的折腾。5.1 坑1看了答案就会合上答案就忘现象打开题解觉得“原来这么简单”但第二天碰到同一道题还是写不出完整代码。原因把答案当成了阅读材料而不是练习材料。大脑在“看懂”的时候会产生错觉以为“看懂”等于“会写”实际上写代码需要的是从输入到输出的完整路径包括边界条件和语法细节。看答案时跳过了卡顿也就跳过了记忆巩固的过程。解决答案只准当裁判不准当老师。每道题先独立写15到20分钟卡住时只看题解的第一段思路描述然后合上屏幕自己写写完提交通过后隔一天再重写一遍。如果隔天还能流畅写出来这道题才算过。5.2 坑2只重数量不重复杂度面试现场翻车现象刷了很多题每道题都能通过但面试官问“这个解法的时间复杂度是多少”“能不能优化”时当场愣住。原因刷题工具默认只显示“通过或不通过”不强制你思考复杂度。时间久了就把“能跑”当成了“会做”忽略了算法核心之一复杂度分析。热题100里很多题目不止一种解法答案里通常给了暴力解和最优解的对比如果只看最优解代码你对“为什么必须这样”的理解会很薄。解决每道题提交后强制回答三个问题时间复杂度和空间复杂度分别是什么能否用更简单但更差的解法来理解最坏情况长什么样不需要写长篇分析只要在笔记里留一行。这行字会迫使你在脑子里把代码重新过一遍复杂度就不再是抽象概念。5.3 坑3边界条件漏处理导致“通过大部分用例”现象本地测试一切正常一提交就报数组越界或者空输入直接异常有时候是链表的头节点为空忘记判断。原因写核心逻辑时只盯着“常规输入”没有在动手前列出边界用例。边界条件不是靠运气想的而是依赖固定的清单。链表题的头节点为空、数组题的长度为0或1、字符串题的空串、数学题的负数和大数溢出这些都应该是在读题时就标记好的。解决写代码之前先花一分钟列出三个边界输入和预期输出。比如做两数之和时先想 nums 为空、nums 长度为1、结果不存在的情况做反转链表时先想 head 为 null 和链表只有一个节点的情况。把边界判断写在代码开头或者用 if 提前返回能避免大多数“通过大部分用例”的尴尬。5.4 坑4死磕难题基础题还没吃透现象花两个晚上啃一道困难题终于通过了但同一周的简单题还是写得磕磕绊绊面试时连基础题都紧张。原因把刷题当成了游戏通关专注于“战胜难题”的成就感忘了热题100的核心目的是覆盖高频考点。困难题往往会混合多个知识点如果基础题没吃透困难题只能靠硬背答案时间投入产出比很低。解决把难题标高先保基础。热题100里简单和中等级别的题要刷到“闭眼能写”的程度再碰困难题。如果一道题超过1.5到2小时没有任何进展立刻跳过把它加入“待回头”列表。只要基础题稳了困难题的正确率会自然上升。5.5 坑5不注意题目变式换个问法就懵现象刷完“两数之和”遇到“三数之和”完全没思路刷完“反转链表”遇到“反转链表 II”又无从下手。原因只记了当前题的答案没有总结“这道题的解法为什么适用”以及“改一个条件后解法可能怎么变”。热题100的题目编号是固定的但面试官非常喜欢在原有题上改条件比如输入从数组变链表、从只有一个答案变成要求所有答案、从无重复变成有重复任何一个改动都会让原答案失效。解决每完成一题问自己三个变式输入有序怎么办要求输出所有可能怎么办有重复元素怎么办不用真的把所有变式都写一遍只要把答案用一句话写在笔记里。这一句话能培养你在面试现场的“变式敏感度”看到新条件时自动联想到旧题的某个解法。6. 把热题100变成自己的模板库一个可复用的复习技巧最后的这个技巧是我在刷完第一轮热题100之后才总结出来的不要按题目复习要按模板复习。具体做法是准备一个笔记文件按照算法类别建条目每个条目写四行适用信号、模板骨架、复杂度、变式。比如链表类的条目写“需要修改链表结构时优先想虚拟头节点骨架是反转链表和快慢指针O(n) 时间 O(1) 空间变式是反转链表 II 需要先定位再反转”。刷第二轮时你不需要再把100道题全看一遍只需要每天抽一条模板在纸上默写。能写出来说明这一类题都捡起来了写不出来就回去翻对应那几道题的代码。这种复习方式效率很高我面试前一周光靠复习十条模板和三十行核心代码就保持了手感。这里要特别提醒模板不是让你写一堆“面试会问什么”的空话而是必须具体到代码骨架和触发条件。比如滑动窗口模板触发条件是“求连续子数组或子串的最值”骨架就是“右进左出加一个计数器”。如果你只写“双指针法很常用”那这模板没有用。每一条模板都要能用一句话回答“什么时候用代码长什么样”。我最后悔的是第一遍刷题时只顾着通过没有顺手建模板库后来改成这个习惯才觉得热题100真正长在了自己身上。每次面试前我都只翻那几页模板心里很踏实。希望帮到你。本文还有配套的精品资源点击获取