LeetCode 面试经典 150 题速查表
本文档整理了 LeetCode 面试经典 150 题中的核心算法技巧和解题模板,方便快速查阅。
双指针技巧
适用场景
- 有序数组查找
- 回文判断
- 链表问题(快慢指针)
- 滑动窗口
常用模板
// 对撞指针:有序数组两数之和
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == target) return new int[]{left, right};
else if (sum < target) left++;
else right--;
}
// 快慢指针:链表中点
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
}
// 快慢指针:环检测
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) return true;
}
return false;
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 125 | 验证回文串 | 对撞指针 |
| 167 | 两数之和 II | 对撞指针 |
| 11 | 盛最多水的容器 | 对撞指针 |
| 15 | 三数之和 | 对撞指针 + 排序 |
| 141 | 环形链表 | 快慢指针 |
| 19 | 删除链表倒数第N个 | 快慢指针 |
滑动窗口
适用场景
- 子串/子数组问题
- 最长/最短满足条件的连续区间
- 频率统计
常用模板
// 基础滑动窗口
int left = 0;
for (int right = 0; right < n; right++) {
// 扩大窗口
window.add(nums[right]);
// 收缩窗口
while (需要收缩) {
window.remove(nums[left]);
left++;
}
// 更新结果
}
// 无重复字符的最长子串
int[] count = new int[128];
int left = 0, maxLen = 0;
for (int right = 0; right < s.length(); right++) {
count[s.charAt(right)]++;
while (count[s.charAt(right)] > 1) {
count[s.charAt(left++)]--;
}
maxLen = Math.max(maxLen, right - left + 1);
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 3 | 无重复字符的最长子串 | 哈希表 + 滑动窗口 |
| 209 | 长度最小的子数组 | 变长滑动窗口 |
| 76 | 最小覆盖子串 | 字符计数 + 滑动窗口 |
二分查找
适用场景
- 有序数组查找
- 寻找边界
- 答案的二分枚举
常用模板
// 标准二分查找
int binarySearch(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
else if (nums[mid] < target) left = mid + 1;
else right = mid - 1;
}
return -1;
}
// 查找左边界(第一个 >= target)
int lowerBound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] >= target) right = mid;
else left = mid + 1;
}
return left;
}
// 查找右边界(最后一个 <= target)
int upperBound(int[] nums, int target) {
int left = 0, right = nums.length;
while (left < right) {
int mid = left + (right - left) / 2;
if (nums[mid] > target) right = mid;
else left = mid + 1;
}
return left - 1;
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 35 | 搜索插入位置 | 左边界 |
| 74 | 搜索二维矩阵 | 二维转一维 |
| 33 | 搜索旋转排序数组 | 旋转数组二分 |
| 34 | 在排序数组中查找元素的第一个和最后一个位置 | 左右边界 |
| 153 | 寻找旋转排序数组中的最小值 | 旋转数组二分 |
| 4 | 寻找两个正序数组的中位数 | 二分答案 |
哈希表
适用场景
- 两数之和类问题
- 字符/元素统计
- 去重
- 快速查找
常用模板
// 两数之和
Map<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
if (map.containsKey(target - nums[i])) {
return new int[]{map.get(target - nums[i]), i};
}
map.put(nums[i], i);
}
// 字符统计
int[] count = new int[26]; // 小写字母
for (char c : s.toCharArray()) {
count[c - 'a']++;
}
// 异位词判断
int[] count = new int[26];
for (char c : s) count[c - 'a']++;
for (char c : t) count[c - 'a']--;
for (int n : count) if (n != 0) return false;
return true;
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 1 | 两数之和 | HashMap |
| 49 | 字母异位词分组 | 排序/计数分组 |
| 128 | 最长连续序列 | HashSet |
| 202 | 快乐数 | HashSet检测循环 |
栈与队列
适用场景
- 括号匹配
- 表达式求值
- 单调栈(下一个更大元素)
- BFS层序遍历
常用模板
// 有效括号
Stack<Character> stack = new Stack<>();
for (char c : s.toCharArray()) {
if (c == '(' || c == '[' || c == '{') {
stack.push(c);
} else {
if (stack.isEmpty() || !match(stack.pop(), c)) return false;
}
}
return stack.isEmpty();
// 单调栈:下一个更大元素
int[] result = new int[n];
Arrays.fill(result, -1);
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
result[stack.pop()] = nums[i];
}
stack.push(i);
}
// 单调队列:滑动窗口最大值
Deque<Integer> deque = new LinkedList<>();
for (int i = 0; i < n; i++) {
while (!deque.isEmpty() && deque.peekFirst() < i - k + 1) {
deque.pollFirst();
}
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offerLast(i);
if (i >= k - 1) result[i - k + 1] = nums[deque.peekFirst()];
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 20 | 有效的括号 | 栈 |
| 155 | 最小栈 | 辅助栈 |
| 150 | 逆波兰表达式求值 | 栈 |
| 224 | 基本计算器 | 栈 |
链表
适用场景
- 反转链表
- 环检测
- 合并链表
- 删除节点
常用模板
// 反转链表
ListNode prev = null, curr = head;
while (curr != null) {
ListNode next = curr.next;
curr.next = prev;
prev = curr;
curr = next;
}
return prev;
// 合并两个有序链表
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (l1 != null && l2 != null) {
if (l1.val < l2.val) {
tail.next = l1;
l1 = l1.next;
} else {
tail.next = l2;
l2 = l2.next;
}
tail = tail.next;
}
tail.next = l1 != null ? l1 : l2;
return dummy.next;
// 链表排序(归并)
ListNode sortList(ListNode head) {
if (head == null || head.next == null) return head;
ListNode mid = getMid(head);
ListNode right = sortList(mid.next);
mid.next = null;
ListNode left = sortList(head);
return merge(left, right);
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 21 | 合并两个有序链表 | 双指针 |
| 2 | 两数相加 | 模拟 |
| 25 | K个一组翻转链表 | 递归/迭代 |
| 146 | LRU缓存 | HashMap + 双向链表 |
二叉树
遍历方式
| 遍历 | 顺序 | 应用 |
|---|---|---|
| 前序 | 根-左-右 | 复制树、序列化 |
| 中序 | 左-根-右 | BST有序遍历 |
| 后序 | 左-右-根 | 计算高度、删除 |
| 层序 | BFS | 最短路径、层级 |
常用模板
// DFS递归
void dfs(TreeNode root) {
if (root == null) return;
// 前序位置
dfs(root.left);
// 中序位置
dfs(root.right);
// 后序位置
}
// BFS层序遍历
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);
}
}
// 最近公共祖先
TreeNode lca(TreeNode root, TreeNode p, TreeNode q) {
if (root == null || root == p || root == q) return root;
TreeNode left = lca(root.left, p, q);
TreeNode right = lca(root.right, p, q);
if (left != null && right != null) return root;
return left != null ? left : right;
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 104 | 二叉树的最大深度 | DFS/BFS |
| 226 | 翻转二叉树 | 递归 |
| 101 | 对称二叉树 | 递归比较 |
| 124 | 二叉树中的最大路径和 | 后序遍历 |
| 236 | 最近公共祖先 | 递归 |
动态规划
一维DP
| 问题类型 | 状态定义 | 转移方程 |
|---|---|---|
| 爬楼梯 | dp[i] 到第i阶的方法数 | dp[i] = dp[i-1] + dp[i-2] |
| 打家劫舍 | dp[i] 前i家的最大金额 | dp[i] = max(dp[i-1], dp[i-2]+nums[i]) |
| 最长递增子序列 | dp[i] 以i结尾的最长 | dp[i] = max(dp[j]+1) if nums[j] < nums[i] |
常用模板
// 一维DP(空间优化)
int prev2 = 0, prev1 = 0;
for (int num : nums) {
int curr = Math.max(prev1, prev2 + num);
prev2 = prev1;
prev1 = curr;
}
// LIS(O(n log n))
int[] dp = new int[n];
int len = 0;
for (int num : nums) {
int i = Arrays.binarySearch(dp, 0, len, num);
if (i < 0) i = -(i + 1);
dp[i] = num;
if (i == len) len++;
}
// 二维DP:最小路径和
int[][] dp = new int[m][n];
dp[0][0] = grid[0][0];
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (i == 0 && j == 0) continue;
dp[i][j] = grid[i][j] + Math.min(
i > 0 ? dp[i-1][j] : Integer.MAX_VALUE,
j > 0 ? dp[i][j-1] : Integer.MAX_VALUE
);
}
}
题目索引
| 题号 | 题目 | 类型 |
|---|---|---|
| 70 | 爬楼梯 | 一维DP |
| 198 | 打家劫舍 | 一维DP |
| 300 | 最长递增子序列 | 一维DP |
| 322 | 零钱兑换 | 完全背包 |
| 139 | 单词拆分 | 背包变形 |
| 62 | 不同路径 | 二维DP |
| 64 | 最小路径和 | 二维DP |
| 72 | 编辑距离 | 二维DP |
回溯算法
适用场景
- 子集、排列、组合
- 棋盘问题(N皇后)
- 路径搜索
常用模板
// 子集
void backtrack(int[] nums, int start, List<Integer> path) {
result.add(new ArrayList<>(path));
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtrack(nums, i + 1, path);
path.remove(path.size() - 1);
}
}
// 排列(有重复)
void backtrack(int[] nums, boolean[] used, List<Integer> path) {
if (path.size() == nums.length) {
result.add(new ArrayList<>(path));
return;
}
for (int i = 0; i < nums.length; i++) {
if (used[i] || (i > 0 && nums[i] == nums[i-1] && !used[i-1])) continue;
used[i] = true;
path.add(nums[i]);
backtrack(nums, used, path);
path.remove(path.size() - 1);
used[i] = false;
}
}
// 组合总和
void backtrack(int[] candidates, int target, int start, List<Integer> path) {
if (target == 0) {
result.add(new ArrayList<>(path));
return;
}
for (int i = start; i < candidates.length; i++) {
if (candidates[i] > target) break;
path.add(candidates[i]);
backtrack(candidates, target - candidates[i], i, path); // 可重复
path.remove(path.size() - 1);
}
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 46 | 全排列 | 回溯 |
| 78 | 子集 | 回溯 |
| 39 | 组合总和 | 回溯 + 剪枝 |
| 22 | 括号生成 | 回溯 |
| 79 | 单词搜索 | DFS回溯 |
图论
基本概念
| 算法 | 时间复杂度 | 适用场景 |
|---|---|---|
| DFS | O(V+E) | 连通性、路径搜索 |
| BFS | O(V+E) | 最短路径、层序遍历 |
| 拓扑排序 | O(V+E) | 依赖关系、课程安排 |
| 并查集 | O(α(n)) | 连通性、等价类 |
常用模板
// DFS
void dfs(int[][] graph, int node, boolean[] visited) {
visited[node] = true;
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
dfs(graph, neighbor, visited);
}
}
}
// BFS最短路径
int bfs(int[][] graph, int start, int end) {
Queue<Integer> queue = new LinkedList<>();
boolean[] visited = new boolean[n];
queue.offer(start);
visited[start] = true;
int steps = 0;
while (!queue.isEmpty()) {
int size = queue.size();
for (int i = 0; i < size; i++) {
int node = queue.poll();
if (node == end) return steps;
for (int neighbor : graph[node]) {
if (!visited[neighbor]) {
visited[neighbor] = true;
queue.offer(neighbor);
}
}
}
steps++;
}
return -1;
}
// 拓扑排序
int[] topologicalSort(int numCourses, int[][] prerequisites) {
List<List<Integer>> graph = new ArrayList<>();
int[] inDegree = new int[numCourses];
for (int i = 0; i < numCourses; i++) graph.add(new ArrayList<>());
for (int[] pre : prerequisites) {
graph.get(pre[1]).add(pre[0]);
inDegree[pre[0]]++;
}
Queue<Integer> queue = new LinkedList<>();
for (int i = 0; i < numCourses; i++) {
if (inDegree[i] == 0) queue.offer(i);
}
int[] result = new int[numCourses];
int index = 0;
while (!queue.isEmpty()) {
int course = queue.poll();
result[index++] = course;
for (int next : graph.get(course)) {
if (--inDegree[next] == 0) queue.offer(next);
}
}
return index == numCourses ? result : new int[0];
}
// 并查集
class UnionFind {
int[] parent, rank;
UnionFind(int n) {
parent = new int[n];
rank = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
}
int find(int x) {
if (parent[x] != x) parent[x] = find(parent[x]);
return parent[x];
}
void union(int x, int y) {
int px = find(x), py = find(y);
if (px == py) return;
if (rank[px] < rank[py]) parent[px] = py;
else if (rank[px] > rank[py]) parent[py] = px;
else { parent[py] = px; rank[px]++; }
}
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 200 | 岛屿数量 | DFS/BFS |
| 207 | 课程表 | 拓扑排序 |
| 399 | 除法求值 | 并查集/DFS |
位运算
常用技巧
| 操作 | 代码 | 说明 |
|---|---|---|
| 判断奇偶 | (n & 1) == 1 | 最低位为1是奇数 |
| 交换两数 | a ^= b; b ^= a; a ^= b; | 不用临时变量 |
| 判断2的幂 | (n & (n - 1)) == 0 | 只有一个1 |
| 统计1的个数 | while (n != 0) { n &= n - 1; count++; } | Brian Kernighan |
| 取最低位1 | n & (-n) | 获取最低位的1 |
常用模板
// 只出现一次的数字
int singleNumber(int[] nums) {
int result = 0;
for (int num : nums) result ^= num;
return result;
}
// 只出现一次的数字 II(其他出现3次)
int singleNumber(int[] nums) {
int ones = 0, twos = 0;
for (int num : nums) {
ones = (ones ^ num) & ~twos;
twos = (twos ^ num) & ~ones;
}
return ones;
}
// 位1的个数
int hammingWeight(int n) {
int count = 0;
while (n != 0) {
n &= n - 1;
count++;
}
return count;
}
题目索引
| 题号 | 题目 | 技巧 |
|---|---|---|
| 136 | 只出现一次的数字 | 异或 |
| 191 | 位1的个数 | Brian Kernighan |
| 190 | 颠倒二进制位 | 位运算 |
复杂度参考
| 复杂度 | 数据规模 | 示例算法 |
|---|---|---|
| O(1) | 无限 | 哈希表查找 |
| O(log n) | 10^18 | 二分查找 |
| O(n) | 10^7 | 遍历、哈希 |
| O(n log n) | 10^6 | 排序 |
| O(n²) | 10^4 | 双重循环 |
| O(n³) | 500 | 三重循环 |
| O(2^n) | 25 | 子集枚举 |
| O(n!) | 12 | 全排列 |
常见错误避免
边界条件
- 空数组、空字符串、空链表
- 单元素、两元素情况
- 负数、零的处理
- 整数溢出(用 long)
细节问题
- 循环边界:
<vs<= - 数组下标从0还是1开始
- 深拷贝 vs 浅拷贝
- 字符串比较用
equals()
时间空间权衡
- 哈希表:空间换时间
- 预处理:前期投入换取查询优化
- 记忆化:递归+缓存
- 滚动数组:压缩空间