LeetCode 解题模板——核心建模 + 常用 API 速查
原则:一个题型一个主模板,变体写成「改哪一行」;多算法题型拆成独立小节。 每个题型先讲「核心建模」——这题抽象成什么模型、最该关心哪 1-2 个问题,把建模想清楚,模板自然就出来了。 末尾附「常用 API 和类 + Lambda 表达式」速查表,方便复习。
一、哈希表
核心建模
抽象成「Map<标识, 信息>」,O(1) 查「某个标识是否出现过、对应什么信息」。 最该关心两件事:key 是什么(用什么唯一标识一个东西)、value 是什么(要返回/统计什么)。
- 两数之和:key=元素值,value=下标(因为要返回下标)
- 字母异位词分组:key=排序后字符串,value=同组列表
- 最长连续序列:key=数字本身,value 用 Set 的「存在性」即可
主模板
// 两数之和:边遍历边查补数
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[]{};
变量与返回值
map:key=值、value=下标;complement= 需要的补数。- 返回值:找到返回两个下标,没找到返回空。
变体
- 分组:key 改成排序串,value 改成
List。 - 连续序列:先 Set 去重,只从「起点」(
n-1不在 Set)开始扩展。
易错点
- 先查后存(否则「当前元素+自己」误判成一对);分组用排序串作 key。
二、双指针
核心建模
抽象成「两个指针的相对移动」。最该关心:两个指针各代表什么边界、什么时候谁动。 三种移动模式:快慢(同向一快一慢)、相向(两端往中间)、同速(维护区间/原地变换)。
主模板(快慢指针)
ListNode slow = head, fast = head;
while (fast != null && fast.next != null) {
slow = slow.next; fast = fast.next.next;
if (slow == fast) {
ListNode p = head;
while (p != slow) { p = p.next; slow = slow.next; }
return p; // 环入口
}
}
return null;
变量与返回值
slow一步、fast两步;有环必相遇,相遇后 head 和相遇点各放指针等速走,相遇处即环入口。
变体
- 相向(有序数组两数之和):
sum<target左移、>target右移。 - 原地移动零:
slow指「下一个非零该放哪」,fast找非零,swap(nums, slow++, fast)。
易错点
- 快慢
while判fast.next != null;相向 O(n) 前提是有序。
三、滑动窗口
核心建模
抽象成「[left, right] 合法区间」。最该关心:窗口的「合法性条件」是什么、不合法时怎么收缩。
主模板(变长窗口)
int left = 0, right = 0, max = 0;
Set<Character> win = new HashSet<>();
while (right < s.length()) {
char c = s.charAt(right);
while (win.contains(c)) { win.remove(s.charAt(left)); left++; } // 收缩到合法
win.add(c);
max = Math.max(max, right - left + 1);
right++;
}
变量与返回值
left/right窗口闭区间,win窗口内容;循环不变量:任何时刻窗口都合法。返回max。
变体
- 定长窗口:窗口大小固定,右移时「右边加一、左边减一」。
- 最小覆盖子串:加
valid计数判断覆盖。
易错点
- 顺序「扩大 → 收缩 → 更新答案」别乱。
四、前缀和
核心建模
抽象成「pre[i] = 前 i 项和」。最该关心:任意区间 [i,j] 的和 = pre[j+1] - pre[i],怎么用这个「差」。
主模板
int[] pre = new int[n + 1]; // pre[0] = 0
for (int i = 0; i < n; i++) pre[i + 1] = pre[i] + nums[i];
// 区间 [i, j] 和 = pre[j+1] - pre[i]
变量与返回值
pre[i]= 前 i 项和(不含 nums[i]),长度n+1,pre[0]=0。返回pre数组。
变体
- 和为 K 的子数组个数:加哈希,
cnt.put(0,1),ans += cnt.get(sum-k),再cnt.put(sum)。
易错点
- 长度
n+1、pre[0]=0;和为 K 先put(0,1),且先查后存。
五、普通数组
核心建模
抽象成「一次遍历 + 维护滚动状态」。最该关心:遍历到 i 时,维护的「状态」是什么、怎么从 i-1 推过来。
5.1 Kadane(最大子数组和)
int cur = nums[0], max = nums[0];
for (int i = 1; i < nums.length; i++) {
cur = Math.max(nums[i], cur + nums[i]); // 接上 or 重新开始
max = Math.max(max, cur);
}
cur= 以 i 结尾的最大和,初始化nums[0](防全负)。返回max。
5.2 轮转数组(三次反转)
reverse(nums, 0, n-1); reverse(nums, 0, k-1); reverse(nums, k, n-1); // k 先 k %= n
5.3 除自身以外数组的乘积(前后缀)
int[] res = new int[n]; res[0] = 1;
for (int i = 1; i < n; i++) res[i] = res[i-1] * nums[i-1]; // 左前缀积
int right = 1;
for (int i = n-1; i >= 0; i--) { res[i] *= right; right *= nums[i]; }
六、区间问题
核心建模
抽象成「区间 [start, end]」。最该关心:按起点还是终点排序、两个区间重叠的判断条件(it.start <= last.end 即重叠)。
6.1 合并区间(按起点排序)
Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // 按起点排序
List<int[]> res = new ArrayList<>();
for (int[] it : intervals)
if (res.isEmpty() || it[0] > res.get(res.size()-1)[1]) res.add(it); // 不重叠新开
else res.get(res.size()-1)[1] = Math.max(res.get(res.size()-1)[1], it[1]); // 扩展
- 重叠判断:
it[0] <= 上一个区间的右边界;不重叠才新开一个。
6.2 区间调度(无重叠区间最多,按终点排序)
Arrays.sort(intervals, (a, b) -> a[1] - b[1]); // 按终点排序(贪心)
int end = intervals[0][1], count = 1;
for (int i = 1; i < intervals.length; i++)
if (intervals[i][0] >= end) { end = intervals[i][1]; count++; }
- 合并用起点排序、调度用终点排序——目的不同:合并要「相邻」,调度要「留最多空间」。
七、矩阵
核心建模
抽象成「二维坐标 + 遍历顺序」。最该关心:用什么顺序遍历、边界怎么收缩、行列的单调性怎么利用。
7.1 螺旋矩阵(四边界收缩)
int top = 0, bottom = m-1, left = 0, right = n-1;
while (top <= bottom && left <= right) {
for (int j = left; j <= right; j++) res.add(matrix[top][j]); top++;
for (int i = top; i <= bottom; i++) res.add(matrix[i][right]); right--;
if (top <= bottom) { for (int j = right; j >= left; j--) res.add(matrix[bottom][j]); bottom--; }
if (left <= right) { for (int i = bottom; i >= top; i--) res.add(matrix[i][left]); left++; }
}
7.2 旋转 90°(转置 + 翻转)
for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) swap(matrix[i][j], matrix[j][i]);
for (int[] row : matrix) reverse(row);
7.3 搜索二维矩阵 II(左下角起步)
int i = m-1, j = 0;
while (i >= 0 && j < n) {
if (matrix[i][j] == target) return true;
else if (matrix[i][j] > target) i--;
else j++;
}
return false;
八、链表
核心建模
抽象成「节点 + next 指针」。最该关心:dummy 哨兵(统一头节点处理)、改指针的顺序(先存再改)、快慢指针(中点/环)。
8.1 反转链表(三指针)
ListNode prev = null, cur = head;
while (cur != null) { ListNode next = cur.next; cur.next = prev; prev = cur; cur = next; }
return prev;
8.2 合并两个有序链表(dummy)
ListNode dummy = new ListNode(-1), cur = dummy;
while (l1 != null && l2 != null) {
if (l1.val <= l2.val) { cur.next = l1; l1 = l1.next; }
else { cur.next = l2; l2 = l2.next; }
cur = cur.next;
}
cur.next = l1 != null ? l1 : l2;
return dummy.next;
8.3 删除倒数第 N 个(dummy + 快慢拉开 n)
ListNode dummy = new ListNode(-1, head), slow = dummy, fast = dummy;
for (int i = 0; i <= n; i++) fast = fast.next;
while (fast != null) { slow = slow.next; fast = fast.next; }
slow.next = slow.next.next;
return dummy.next;
8.4 相交链表(路径补偿)
ListNode a = headA, b = headB;
while (a != b) { a = a != null ? a.next : headB; b = b != null ? b.next : headA; }
return a;
8.5 K 个一组翻转(分组 + 段内反转)
ListNode dummy = new ListNode(-1, head), prev = dummy;
while (true) {
ListNode tail = prev;
for (int i = 0; i < k && tail != null; i++) tail = tail.next;
if (tail == null) break;
ListNode nextGroup = tail.next;
ListNode[] rev = reverse(head, tail); // 返回 [新头, 新尾]
prev.next = rev[0]; rev[1].next = nextGroup;
prev = rev[1]; head = nextGroup;
}
return dummy.next;
九、二叉树
核心建模
抽象成「根 + 左右子树」的递归。最该关心:遍历顺序(前/中/后)决定「处理 root 的时机」、递归返回值的语义。
9.1 递归遍历
void dfs(TreeNode root) {
if (root == null) return;
// 前序:处理 root(复制/构造)
dfs(root.left);
// 中序:处理 root
dfs(root.right);
// 后序:处理 root(自底向上聚合)
}
9.2 自底向上(返回值 ≠ 答案)
int dfs(TreeNode node) {
if (node == null) return 0;
int left = Math.max(dfs(node.left), 0);
int right = Math.max(dfs(node.right), 0);
ans = Math.max(ans, left + right + node.val); // 全局答案
return Math.max(left, right) + node.val; // 返回单侧贡献
}
9.3 前中序构造
TreeNode build(int[] pre, int preL, int preR, int[] in, int inL, int inR) {
if (preL > preR) return null;
TreeNode root = new TreeNode(pre[preL]);
int idx = indexOf(in, pre[preL]);
int leftLen = idx - inL;
root.left = build(pre, preL+1, preL+leftLen, in, inL, idx-1);
root.right = build(pre, preL+leftLen+1, preR, in, idx+1, inR);
return root;
}
十、二叉搜索树(BST)
核心建模
抽象成「左 < 根 < 右」的有序约束。最该关心:中序遍历 = 升序、用上下界约束节点、利用大小关系剪枝/缩小范围。
10.1 验证 BST(传上下界)
boolean isValid(TreeNode node, long min, long max) {
if (node == null) return true;
if (node.val <= min || node.val >= max) return false;
return isValid(node.left, min, node.val) && isValid(node.right, node.val, max);
}
min/max是值域上下界,向下收缩;用long防溢出;只和父节点比会漏判。
10.2 第 K 小元素(中序 = 升序)
// 中序遍历,数到第 k 个即答案
void inorder(TreeNode node) {
if (node == null) return;
inorder(node.left);
if (++count == k) { ans = node.val; return; } // 中序位置计数
inorder(node.right);
}
- BST 中序遍历天然升序,第 K 小 = 中序第 K 个。
十一、图论
核心建模
抽象成「节点 + 邻接关系」。最该关心:问的是连通性/环/最短路/前缀里的哪个,以及「标记已访问」(图会重复遍历,不标记就死循环)。
11.1 DFS 淹没(岛屿/连通分量)
void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] != '1') return;
grid[i][j] = '0'; // 标记已访问
dfs(grid, i+1, j); dfs(grid, i-1, j); dfs(grid, i, j+1); dfs(grid, i, j-1);
}
11.2 BFS(层序 / 多源最短路)
Queue<int[]> q = new ArrayDeque<>();
for (源) q.offer(源); // 多源:所有源同时入队
int step = 0;
while (!q.isEmpty()) {
int size = q.size();
for (int k = 0; k < size; k++) {
int[] cur = q.poll();
for (四方向) if (合法 && 未访问) { 标记; q.offer(新点); }
}
step++;
}
11.3 拓扑排序(Kahn 入度法判环)
int[] indegree = new int[n];
for (int[] e : edges) { adj.get(e[1]).add(e[0]); indegree[e[0]]++; }
Queue<Integer> q = new ArrayDeque<>();
for (int i = 0; i < n; i++) if (indegree[i] == 0) q.offer(i);
int count = 0;
while (!q.isEmpty()) {
int cur = q.poll(); count++;
for (int next : adj.get(cur)) if (--indegree[next] == 0) q.offer(next);
}
return count == n;
11.4 并查集(连通性)
int find(int x) { if (parent[x] != x) parent[x] = find(parent[x]); return parent[x]; }
void union(int a, int b) { parent[find(a)] = find(b); }
十二、回溯
核心建模
抽象成「决策树」。最该关心:选择列表是什么、怎么防止重复(start / used)、撤销选择。
主模板
void backtrack(int[] nums, int start) {
res.add(new ArrayList<>(path));
for (int i = start; i < nums.length; i++) {
path.add(nums[i]);
backtrack(nums, i + 1); // i+1 不回头
path.removeLast();
}
}
变量与返回值
path当前路径、start本轮起点;res.add(new ArrayList<>(path))必须拷贝。
变体
- 组合:加
target条件;排列:删 start 改用boolean[] used;去重:先 sort,跳过i>start && nums[i]==nums[i-1]。
易错点
- 撤销不能漏;
res.add要拷贝(否则 path 被改)。
十三、二分
核心建模
抽象成「有序区间 + 中点 mid」。最该关心:区间开闭定义([left,right] 还是 [left,right))、mid 取法、边界怎么更新——三者必须全程一致。
主模板(左闭右闭精确查找)
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;
变体
- 找左边界:
while (left<right)、right=nums.length,if (nums[mid]>=target) right=mid else left=mid+1,返回left(判越界)。 - 旋转数组找最小值:改成
if (nums[mid] > nums[right]) left=mid+1 else right=mid,返回nums[left]。
易错点
mid防溢出;区间开闭和退出条件配套;找边界返回的left先判越界。
十四、普通栈
核心建模
抽象成「后进先出」。最该关心:什么入栈、什么出栈、出栈前判空。
14.1 有效括号(存期望的右括号)
Deque<Character> stack = new ArrayDeque<>();
for (char c : s.toCharArray()) {
if (c == '(') stack.push(')');
else if (c == '[') stack.push(']');
else if (c == '{') stack.push('}');
else if (stack.isEmpty() || stack.pop() != c) return false;
}
return stack.isEmpty();
14.2 最小栈(辅助栈同步存最小值)
class MinStack {
Deque<Integer> stack = new ArrayDeque<>(), minStack = new ArrayDeque<>();
void push(int x) { stack.push(x); minStack.push(minStack.isEmpty() ? x : Math.min(x, minStack.peek())); }
void pop() { stack.pop(); minStack.pop(); }
int getMin() { return minStack.peek(); }
}
pop要同时 popminStack,否则最小值不同步。
十五、单调栈
核心建模
抽象成「栈内元素单调」。最该关心:存下标还是值(要算距离就存下标)、比较方向(更大/更小)、while 弹出的时机。
主模板(下一个更大元素)
Deque<Integer> stack = new ArrayDeque<>(); // 存下标,栈内值单调递减
for (int i = 0; i < n; i++) {
while (!stack.isEmpty() && t[i] > t[stack.peek()]) {
int idx = stack.pop(); // i 是 idx 的下一个更大
res[idx] = i - idx;
}
stack.push(i);
}
- 结构固定「while 弹出再 push」,变体只改比较方向(更大/更小)。
十六、堆
核心建模
抽象成「极值优先队列」。最该关心:找第 K 大还是第 K 小(决定用小顶堆还是大顶堆)、比较器怎么写。
16.1 TopK(第 K 大用小顶堆)
PriorityQueue<Integer> heap = new PriorityQueue<>(); // 小顶堆
for (int num : nums) {
heap.offer(num);
if (heap.size() > k) heap.poll();
}
return heap.peek();
- 堆里留 K 个最大的,堆顶 = 第 K 大;找第 K 小就换大顶堆。
16.2 数据流中位数(双堆对半切)
PriorityQueue<Integer> lower = new PriorityQueue<>(Collections.reverseOrder()); // 大顶堆
PriorityQueue<Integer> upper = new PriorityQueue<>(); // 小顶堆
void add(int num) {
lower.offer(num); upper.offer(lower.poll());
if (upper.size() > lower.size()) lower.offer(upper.poll());
}
double median() {
return lower.size() > upper.size() ? lower.peek() : (lower.peek() + upper.peek()) / 2.0;
}
十七、贪心
核心建模
抽象成「每一步做当前最优」。最该关心:凭什么敢这么贪(能证明「局部最优 → 全局最优」吗)——面试官必问这个证明。
17.1 区间调度(按终点)
Arrays.sort(intervals, (a, b) -> a[1] - b[1]);
int end = intervals[0][1], count = 1;
for (int i = 1; i < intervals.length; i++)
if (intervals[i][0] >= end) { end = intervals[i][1]; count++; }
17.2 跳跃游戏(维护最远可达)
int maxReach = 0;
for (int i = 0; i < nums.length; i++) {
if (i > maxReach) return false;
maxReach = Math.max(maxReach, i + nums[i]);
}
return true;
17.3 买卖股票一次(维护历史最低)
int minPrice = Integer.MAX_VALUE, maxProfit = 0;
for (int price : prices) {
minPrice = Math.min(minPrice, price);
maxProfit = Math.max(maxProfit, price - minPrice);
}
十八、动态规划
核心建模
抽象成「状态 + 转移」。最该关心:dp[i] 到底代表什么(状态定义)、转移方程、初始化、遍历顺序——四要素缺一不可。
主模板(线性 DP)
int[] dp = new int[n + 1]; dp[0] = 1; dp[1] = 1;
for (int i = 2; i <= n; i++) dp[i] = dp[i-1] + dp[i-2];
return dp[n];
变体
- 01 背包:
dp[v]= 容量 v 最大价值,容量倒序for (v=V; v>=w; v--)(每件用一次)。 - 完全背包:容量正序
for (v=w; v<=V; v++)(无限次)——就这一行不同。
易错点
- 01 倒序、完全正序;初始化边界想清楚。
十九、多维动态规划
核心建模
抽象成「dp[i][j] 二维状态」。最该关心:两个维度分别代表什么、索引偏移(dp[i][j] 对应 s[i-1])、0 行 0 列的边界。
主模板(最长公共子序列)
int[][] dp = new int[m+1][n+1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (s1.charAt(i-1) == s2.charAt(j-1)) dp[i][j] = dp[i-1][j-1] + 1;
else dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
return dp[m][n];
dp[i][j]对应s1[i-1]、s2[j-1](偏移 1),dp[0][..]和dp[..][0]是空串边界。
变体
- 编辑距离:不相等时
dp[i][j] = 1 + min(替换, 删除, 插入)。 - 最长回文子串:
dp[i][j]表示s[i..j]是否回文,按长度从小到大填。
二十、字典树(Trie)
核心建模
抽象成「字符树」。最该关心:children 数组(26 个字母)、isEnd 标记单词结束、逐字符下钻。
主模板
class TrieNode { TrieNode[] children = new TrieNode[26]; boolean isEnd; }
void insert(String word) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) node.children[idx] = new TrieNode();
node = node.children[idx];
}
node.isEnd = true;
}
boolean search(String word) { // 完整单词
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.children[c - 'a'];
if (node == null) return false;
}
return node.isEnd;
}
boolean startsWith(String prefix) { // 前缀
TrieNode node = root;
for (char c : prefix.toCharArray()) {
node = node.children[c - 'a'];
if (node == null) return false;
}
return true;
}
isEnd区分「前缀」和「完整单词」;children[26]只适用小写字母。
二十一、技巧题
核心建模
抽象成「利用题目隐藏性质」。最该关心:题目给了什么特殊条件(只出现一次/多数/值域范围),顺着条件把空间降到 O(1)、时间降到 O(n)。
21.1 只出现一次(异或)
int single = 0; for (int n : nums) single ^= n; // a^a=0, a^0=a
21.2 多数元素(摩尔投票)
int cand = nums[0], count = 1;
for (int i = 1; i < nums.length; i++) {
if (count == 0) cand = nums[i];
count += (nums[i] == cand) ? 1 : -1;
}
21.3 缺失的第一个正数(原地哈希)
for (int i = 0; i < n; i++)
while (nums[i] > 0 && nums[i] <= n && nums[nums[i]-1] != nums[i])
swap(nums, i, nums[i]-1);
for (int i = 0; i < n; i++) if (nums[i] != i+1) return i+1;
21.4 颜色分类(荷兰国旗三指针)
int low = 0, high = n-1, cur = 0;
while (cur <= high) {
if (nums[cur] == 0) swap(nums, low++, cur++);
else if (nums[cur] == 2) swap(nums, cur, high--);
else cur++;
}
快速判断:这题套哪个模板?
| 题目特征 | 套路 |
|---|---|
| 两数之和 / 分组 / 计数 | 哈希表 |
| 有序两端收缩 / 链表环 / 原地去重 | 双指针 |
| 连续子串 / 子数组区间 | 滑动窗口 |
| 区间和 / 和为 K | 前缀和 |
| 子数组最值 / 轮转 / 前后缀 | 普通数组 |
| 合并区间 / 区间调度 | 区间问题 |
| 螺旋 / 旋转 / 行列有序搜索 | 矩阵 |
| 反转 / 合并 / 删除 / K 组 / 相交 | 链表 |
| 遍历 / 深度 / 构造 / LCA | 二叉树 |
| 验证 / 第 K 小 / 范围查询 | 二叉搜索树 |
| 连通 / 环 / 拓扑 / 最短路 | 图论 |
| 枚举所有组合排列子集 | 回溯 |
| 有序找数 / 边界 / 旋转数组 | 二分 |
| 括号 / 最小值 | 普通栈 |
| 下一个更大 / 窗口最大值 | 单调栈 |
| 第 K 大 / 前 K 高频 / 中位数 | 堆 |
| 局部最优能证全局最优 | 贪心 |
| 一维最值可拆子问题 | 动态规划 |
| 两个序列 / 二维状态 | 多维动态规划 |
| 字符串前缀 / 单词查找 | 字典树 |
| 位运算 / 投票 / 原地哈希 / 三路划分 | 技巧题 |
附一、输入输出(面试 main 类 + Scanner)
面试(牛客/华为 OD 等 ACM 模式)给你一个
Main类,需要自己读入参数、输出结果。
标准模板
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
// 读输入 → 处理 → 输出
sc.close();
}
}
常见输入格式
int n = sc.nextInt(); // 读一个整数
String s = sc.next(); // 读一个 token(到空格/换行停)
String line = sc.nextLine(); // 读一整行
int[] arr = new int[n];
for (int i = 0; i < n; i++) arr[i] = sc.nextInt(); // 读 n 个整数
// 读一个字符串数组(空格分隔)
String[] strs = sc.nextLine().split(" ");
// 不定量输入:读到 EOF
while (sc.hasNextInt()) { int x = sc.nextInt(); ... }
// 多组测试用例
int t = sc.nextInt();
while (t-- > 0) { /* 每组处理 */ }
// 读二维矩阵 m 行 n 列
int m = sc.nextInt(), n = sc.nextInt();
int[][] grid = new int[m][n];
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) grid[i][j] = sc.nextInt();
输出
System.out.println(x); // 换行输出
System.out.print(x + " "); // 不换行
System.out.printf("%.2f", d); // 格式化(保留两位小数)
// 数组输出
System.out.println(Arrays.toString(arr)); // [1, 2, 3]
// List 输出
System.out.println(list); // [1, 2, 3]
// 字符串数组一行输出
System.out.println(String.join(" ", strs));
注意坑
nextInt()/next()后面如果接nextLine(),要先多调一次sc.nextLine()吞掉上一行末尾的换行符,否则会读到空串。- 大数组用
StringBuilder拼输出,避免反复print。
快读(大数据量时,Scanner 慢)
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String line = br.readLine();
String[] parts = line.split(" ");
int n = Integer.parseInt(parts[0]);
附二、JDK8 常用类与 API 全收集(LeetCode Hot 100 全覆盖)
数组 Arrays
Arrays.sort(arr); // 升序
Arrays.sort(arr, from, to); // 部分排序 [from, to)
Arrays.sort(arr, (a, b) -> b - a); // 降序(对象数组/包装类)
Arrays.fill(arr, val); // 填充
Arrays.copyOf(arr, len); // 复制(可扩容)
Arrays.copyOfRange(arr, from, to);
Arrays.equals(a, b); // 数组相等(定长窗口判断用)
Arrays.binarySearch(arr, key); // 二分查找(需先排序)
集合 Collections
Collections.sort(list); // 排序
Collections.reverse(list); // 反转
Collections.reverseOrder(); // 降序比较器(大顶堆用)
Collections.min(list) / Collections.max(list);
Collections.swap(list, i, j); // 交换(回溯/排列用)
List(ArrayList / LinkedList)
list.add(e); list.add(i, e); list.get(i); list.set(i, e);
list.remove(i); list.remove(e); list.size(); list.isEmpty();
list.contains(e); list.indexOf(e); list.clear();
list.subList(from, to); // 子列表
Map(HashMap / TreeMap)
map.put(k, v); map.get(k); map.getOrDefault(k, def);
map.containsKey(k); map.remove(k); map.size(); map.isEmpty();
map.keySet(); map.values(); map.entrySet();
map.putIfAbsent(k, v); // 不存在才放
map.merge(k, 1, Integer::sum); // 累加计数(一行)
map.computeIfAbsent(k, x -> new ArrayList<>()).add(v); // 分组收集
// 遍历
for (Map.Entry<K, V> e : map.entrySet()) { K k = e.getKey(); V v = e.getValue(); }
for (K k : map.keySet()) { }
Set(HashSet / TreeSet)
set.add(e); set.remove(e); set.contains(e); set.size(); set.isEmpty();
// TreeSet:有序集合,first()/last()/ceiling()/floor()(值域问题用)
字符串 String / StringBuilder
s.charAt(i); s.length(); s.substring(i); s.substring(i, j); // [i, j)
s.toCharArray(); s.equals(t); s.equalsIgnoreCase(t);
s.indexOf(c); s.lastIndexOf(c); s.contains(sub);
s.startsWith(p) / s.endsWith(p);
s.split(regex); s.trim(); s.replace(a, b);
s.toLowerCase() / s.toUpperCase(); s.compareTo(t);
// StringBuilder(频繁拼接/反转用,别用 + 循环拼接)
sb.append(x); sb.insert(i, x); sb.deleteCharAt(i);
sb.reverse(); sb.toString(); sb.length(); sb.charAt(i); sb.setCharAt(i, c);
字符 Character
Character.isDigit(c); isLetter(c); isLetterOrDigit(c);
Character.isLowerCase(c); isUpperCase(c);
Character.toLowerCase(c); toUpperCase(c);
Character.getNumericValue(c); // 字符数字 '5' → int 5
包装类 Integer / Long / Double
Integer.parseInt(s); Integer.valueOf(s); // 字符串 → 整数
Integer.MAX_VALUE / Integer.MIN_VALUE;
Integer.compare(a, b); // 比较(防溢出,排序用)
Integer.bitCount(x); // 二进制 1 的个数
Long.parseLong(s); Double.parseDouble(s);
Integer.toString(x); // 整数 → 字符串
数学 Math
Math.max(a, b); Math.min(a, b); Math.abs(x);
Math.pow(a, b); Math.sqrt(x); Math.log(x);
Math.ceil(x); Math.floor(x); Math.round(x);
栈 / 队列(Deque = ArrayDeque,别用 Stack)
Deque<Integer> dq = new ArrayDeque<>();
// 作栈(后进先出)
dq.push(e); dq.pop(); dq.peek();
// 作队列(先进先出)
dq.offer(e); dq.poll(); dq.peek();
// 作双端队列
dq.offerFirst(e); dq.offerLast(e); dq.pollFirst(); dq.pollLast();
dq.peekFirst(); dq.peekLast(); dq.size(); dq.isEmpty();
堆 PriorityQueue
PriorityQueue<Integer> minHeap = new PriorityQueue<>(); // 小顶堆(默认)
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // 大顶堆
PriorityQueue<int[]> heap = new PriorityQueue<>((a, b) -> a[1] - b[1]); // 自定义比较器
heap.offer(e); heap.poll(); heap.peek(); heap.size(); heap.isEmpty();
位运算
x & 1 // 奇偶判断(1 奇 0 偶)
x & -x // lowbit:最低位的 1
x & (x - 1) // 消掉最低位的 1(统计 1 个数用)
x ^ x // 归零(异或性质)
x << 1 / x >> 1 // 乘 2 / 除 2
Lambda 表达式常用场景
// 比较器
Arrays.sort(intervals, (a, b) -> a[0] - b[0]); // 按第一列升序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0])); // 防溢出写法
Collections.sort(list, (a, b) -> b - a); // 降序
// 堆比较器
new PriorityQueue<>((a, b) -> a[1] - b[1]); // 按频率/第二维
// 分组 / 计数
map.computeIfAbsent(key, k -> new ArrayList<>()).add(v);
map.merge(key, 1, Integer::sum);
// 方法引用
list.sort(Comparator.comparingInt(a -> a[0])); // 按字段排序
list.forEach(x -> System.out.println(x));
易混淆对照
| 场景 | 用什么 | 关键点 |
|---|---|---|
| 栈(后进先出) | Deque + push/pop/peek | 别用已废弃的 Stack |
| 队列(先进先出) | Deque + offer/poll/peek | 别用 add/remove(会抛异常) |
| 第 K 大 | 小顶堆 PriorityQueue(默认) | 找第 K 小才用大顶堆 |
| 自定义排序 | Integer.compare(a,b) | a-b 可能溢出 |
| 计数累加 | map.merge(k,1,Integer::sum) | 比 getOrDefault+put 简洁 |
| 分组收集 | computeIfAbsent | 比手动判空 new 简洁 |
| 字符串反转 | new StringBuilder(s).reverse() | String 没有 reverse 方法 |