跳到主要内容

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两数相加模拟
25K个一组翻转链表递归/迭代
146LRU缓存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回溯

图论

基本概念

算法时间复杂度适用场景
DFSO(V+E)连通性、路径搜索
BFSO(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
取最低位1n & (-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()

时间空间权衡

  • 哈希表:空间换时间
  • 预处理:前期投入换取查询优化
  • 记忆化:递归+缓存
  • 滚动数组:压缩空间