面试必备:LeetCode HOT 100 分类刷题指南
如今的互联网大厂面试,算法是绕不开的一关。对于时间紧迫的求职者来说,盲目刷题无异于大海捞针。正确的做法是先做对题——而LeetCode HOT 100,就是这把打开算法面试大门的钥匙。
一、HOT 100 是什么?
LeetCode HOT 100(热题100)是LeetCode官方基于海量用户提交数据,统计出的被点赞最多、讨论最热烈、出现频率最高的100道题目。这100道题的特别之处在于:只要你真正掌握它们,80%的算法面试都能遇到相似题目。它的知识点覆盖面极广,从基础的数组、链表,到进阶的动态规划、回溯算法,应有尽有,堪称面试算法题的“精华版”。
因此,与其盲目追求刷题数量,不如静下心来把这100道经典题目吃透,真正做到“做一题,会一类”。
二、完整分类体系:一张图看懂算法知识地图
以下是我根据自己对HOT 100的理解(以及整理的多篇文章分类汇总),总结的完整分类体系,建议按照这个顺序刷题。
第1步:哈希
哈希表是面试最高频的基础数据结构,核心思想是“空间换时间”。在实际开发中,我们经常需要快速查找、统计频次、判断元素是否存在,这些都是哈希表的强项。
典型题目:1. 两数之和(E)——边遍历边检查,把O(N²)降到O(N)129. 最长连续序列(M)你可能会对438、560这类题目感到困惑,不明白它们为什么会被归为“哈希”考点。
心法口诀:一看配对就哈希,空间换时间是王道。
第2步:双指针
双指针教你如何把一个 O(N²) 的暴力解法,巧妙地降到 O(N)。这就像武功中的“四两拨千斤”,用最小的代价达到最好的效果。
典型题目:15. 三数之和(M)
心法口诀:有序数组左右夹,原地修改快慢跑。
第3步:滑动窗口
滑动窗口是双指针的高级形态。它教你在 O(N) 的时间内,优雅地穷举所有符合条件的“子串”或“子数组”,堪称“一招制敌”。
典型题目:3. 无重复字符的最长子串(M)
第4步:普通数组
数组是算法大厦的基石,虽然基础,但题型变化极其丰富。通过这些题目,你会学到如何处理循环数组、合并重叠区间、原地哈希等关键技能。
典型题目:56. 合并区间(M)
第5步:矩阵
矩阵是二维数组的“孪生兄弟”,这类题目主要考验边界条件的处理能力。
典型题目:48. 旋转图像(M)
第6步:链表
链表题是面试中的绝对高频区域。这类题的特点是不需要复杂的数学推理,但非常考验对指针操作的熟练度。
典型题目:206. 反转链表(E)、19. 删除链表的倒数第 N 个结点(M)。
第7步:二叉树
作为面试的“分水岭”,树的题目可以很简单,也可以极难。同时,二叉树也是理解递归本质的最好素材。
典型题目:94. 二叉树的中序遍历(E)、102. 二叉树的层序遍历(M)、236. 二叉树的最近公共祖先(M)。
第8步:图论
图论是面试中的进阶题型,通常是解决复杂问题的关键思路。
典型题目:200. 岛屿数量(M)、994. 腐烂的橘子(M)。
第9步:回溯
回溯是暴力枚举的高级形式。当你面对“组合、排列、子集”这类需要穷举所有可能性的问题,回溯是唯一的解法。
典型题目:46. 全排列(M)、22. 括号生成(M)。
第10步:二分查找
二分查找不是简单的“在有序数组里找数”,它的真正威力在于“优化”思想。往往在遇到“最大最小值”问题时,应该尝试二分。
典型题目:33. 搜索旋转排序数组(M)。
第11步:栈
栈和队列是深搜、广搜算法的基础,也是处理括号配对等问题的利器。
典型题目:20. 有效的括号(E)、239. 滑动窗口最大值(H)。
第12步:堆
堆是一类特殊的树结构,用于解决Top K、优先队列等场景。结合哈希表可以轻松统计并获取频率最高的元素。
典型题目:347. 前 K 个高频元素(M)。
第13步:贪心
贪心算法讲究“活在当下”,每次都做局部最优选择,期望得到全局最优结果。
典型题目:55. 跳跃游戏(M)。
第14步:动态规划
这是面试的重头戏。动态规划问题的核心在于两点:一是dp数组的定义,二是状态转移方程的推导。DP题目通常会占整个面试时间的40%,需要重点攻克。
典型题目:53. 最大子数组和(M)、70. 爬楼梯(E)、300. 最长递增子序列(M)。
第15步:技巧
这一块之所以叫“技巧篇”,是因为它考察的是某些特定的思维技巧,比如异或和摩尔投票,只要没写过,很容易在面试中没有思路。
典型题目:136. 只出现一次的数字(E,异或思想)、169. 多数元素(E,摩尔投票法)。
三、六类核心算法详解
限于篇幅,这里挑选六个最高频的算法分类展开详细讲解,附上思路分析和代码模板。
3.1 哈希表
核心思想:利用哈希映射快速查找、统计频率或建立键值对应关系。
经典题目:两数之和(1)
题目:给定一个整数数组nums和一个整数目标值target,请你在该数组中找出和为目标值的那两个整数,并返回它们的数组下标。
思路分析:
最直观的解法是两层循环暴力枚举,时间复杂度 O(N²)。更优的做法是使用哈希表:遍历数组,对于每个元素nums[i],我们检查哈希表中是否存在target - nums[i]。如果存在,直接返回;如果不存在,将当前(nums[i], i)存入哈希表,交给后续的元素去配对。这样只需遍历一次,时间复杂度降为 O(N)。
代码模板:
java
public int[] twoSum(int[] nums, int target) { if (nums == null || nums.length < 2) return new int[0]; Map<Integer, Integer> map = new HashMap<>(); for (int i = 0; i < nums.length; i++) { int complement = target - nums[i]; if (map.containsKey(complement)) { return new int[]{map.get(complement), i}; } map.put(nums[i], i); } return new int[0]; }3.2 双指针
核心思想:通过快慢指针或左右指针缩小搜索范围,降低时间复杂度。
经典题目:三数之和(15)
题目:给你一个整数数组nums,判断是否存在三元组[nums[i], nums[j], nums[k]]满足i != j != k,且nums[i] + nums[j] + nums[k] == 0。请你返回所有和为0且不重复的三元组。
思路分析:
首先对数组进行排序。然后固定一个数nums[i],在i右侧的区间内使用左右双指针寻找两数之和等于-nums[i]。每当找到一组解,移动指针跳过重复元素以避免结果重复。排序的时间复杂度 O(N log N),双指针部分 O(N²),总复杂度 O(N²)。
代码模板:
java
public List<List<Integer>> threeSum(int[] nums) { List<List<Integer>> res = new ArrayList<>(); if (nums == null || nums.length < 3) return res; Arrays.sort(nums); for (int i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] == nums[i - 1]) continue; // 跳过重复 int left = i + 1, right = nums.length - 1; while (left < right) { int sum = nums[i] + nums[left] + nums[right]; if (sum == 0) { res.add(Arrays.asList(nums[i], nums[left], nums[right])); while (left < right && nums[left] == nums[left + 1]) left++; while (left < right && nums[right] == nums[right - 1]) right--; left++; right--; } else if (sum < 0) { left++; } else { right--; } } } return res; }3.3 滑动窗口
核心思想:维护一个窗口在数组/字符串上滑动,右边界负责扩张,左边界负责收缩,用于解决子串/子数组相关的最优问题。
核心思想:维护一个窗口在数组/字符串上滑动,右边界负责扩张,左边界负责收缩,用于解决子串/子数组相关的最优问题。
经典题目:无重复字符的最长子串(3)
题目:给定一个字符串s,请你找出其中不含有重复字符的最长子串的长度。
思路分析:
使用滑动窗口技术。维护一个左指针left指向窗口左边界,一个right指针向右遍历。使用一个HashSet或数组(字符集较小时)记录窗口内已有的字符。当遇到重复字符时,不断移动左指针并删除退出窗口的字符,直到窗口中不再包含当前字符。窗口长度的最大值即为答案。
代码模板:
java
public int lengthOfLongestSubstring(String s) { Set<Character> set = new HashSet<>(); int left = 0, maxLen = 0; for (int right = 0; right < s.length(); right++) { char c = s.charAt(right); while (set.contains(c)) { set.remove(s.charAt(left++)); } set.add(c); maxLen = Math.max(maxLen, right - left + 1); } return maxLen; }3.4 二叉树
核心思想:熟练掌握深度优先搜索和广度优先搜索的不同应用场景。
经典题目:二叉树的层序遍历(102)
题目:给你二叉树的根节点root,返回其节点值的层序遍历(即逐层地,从左到右访问所有节点)。
思路分析:
这是广度优先搜索的经典应用。使用队列来辅助遍历:先将根节点入队。当队列不为空时,记录当前队列长度size(即当前层的节点数),然后循环size次,将这一层的所有节点出队并记录值,同时将它们的左右子节点入队。如此循环直至队列为空,即可得到分层遍历的结果。
代码模板:
java
public List<List<Integer>> levelOrder(TreeNode root) { List<List<Integer>> res = new ArrayList<>(); if (root == null) return res; Queue<TreeNode> queue = new LinkedList<>(); queue.offer(root); while (!queue.isEmpty()) { int size = queue.size(); List<Integer> level = new ArrayList<>(); for (int i = 0; i < size; i++) { TreeNode node = queue.poll(); level.add(node.val); if (node.left != null) queue.offer(node.left); if (node.right != null) queue.offer(node.right); } res.add(level); } return res; }3.5 动态规划
核心思想:将原问题拆解为若干子问题,记录子问题的解以避免重复计算。
经典题目:爬楼梯(70)
题目:假设你正在爬楼梯。需要n阶你才能到达楼顶。每次你可以爬1或2个台阶。你有多少种不同的方法可以爬到楼顶?
思路分析:
到达第i阶台阶的方法数 = 到达第i-1阶的方法数 + 到达第i-2阶的方法数。这是一个斐波那契数列问题。dp[i] = dp[i - 1] + dp[i - 2],初始条件dp[0] = 1,dp[1] = 1。最终答案dp[n]。该方法的时间复杂度为 O(N),空间复杂度为 O(1)。
java
public int climbStairs(int n) { if (n <= 2) return n; int first = 1, second = 2; for (int i = 3; i <= n; i++) { int third = first + second; first = second; second = third; } return second; }3.6 回溯算法
核心思想:回溯法采用深度优先搜索的策略,在搜索过程中不断尝试可能的路径,一旦发现当前路径不可行,就“回溯”到上一步,撤销选择,再尝试其他的路径。
经典题目:全排列(46)
题目:给定一个不含重复数字的数组nums,返回其所有可能的全排列。
思路分析:
使用深度优先搜索加回溯。用一个List记录当前已选择的路径,用一个布尔数组记录哪些元素已被用于本次递归。当路径长度等于数组长度时,将当前路径的副本加入结果集。在递归返回后,需要撤销当前选择,继续探索其他分支。
java
public List<List<Integer>> permute(int[] nums) { List<List<Integer>> res = new ArrayList<>(); boolean[] used = new boolean[nums.length]; backtrack(nums, new ArrayList<>(), used, res); return res; } private void backtrack(int[] nums, List<Integer> path, boolean[] used, List<List<Integer>> res) { if (path.size() == nums.length) { res.add(new ArrayList<>(path)); return; } for (int i = 0; i < nums.length; i++) { if (used[i]) continue; used[i] = true; path.add(nums[i]); backtrack(nums, path, used, res); path.remove(path.size() - 1); used[i] = false; } }四、如何高效刷题
刷题确实有技巧,以下是几个实用建议:
先分类再综合:不要跳着随机刷,按我上面的分类顺序,一个专题一个专题地攻克效果最好。且要先做 Easy 和 Medium,Hard 题可放在每类最后,权当拔高。
先思考再看题解:一道题至少要自己思考 15 分钟,没思路再去看题解。切勿直接背答案,这样很难举一反三。写完题后花几分钟总结复杂度与易错点,才算是真正会了。
重质不重量:与其刷 500 道题只记住答案,不如把 100 道经典题真正吃透。
写解题笔记:每道题记录解题思路、时间空间复杂度、易错点。这是复习时最高效的方式。
五、总结
如果说算法面试是一场“战役”,那么 LeetCode HOT 100 就是你需要攻克的最核心的“根据地”。掌握好这一百道题,相当于建立起来一份完整的算法知识图谱,在面对面试官时便能更有底气。
最后送上一张刷题口诀表,方便记忆:
| 题型特征 | 第一反应算法 |
|---|---|
| 查找、配对、计数 | 哈希表 |
| 有序数组、配对、原地修改 | 双指针 |
| 子串、子数组、连续 | 滑动窗口 |
| 排列、组合、枚举 | 回溯 |
| 最优子结构、重叠子问题 | 动态规划 |
| 树的分层遍历 | 广度优先搜索 |
| 树的深搜、递归 | 深度优先搜索 |
