📚 Part 1: Java 集合使用手册

0.String - 不可变字符串

// 初始化
String s = "hello";
String s2 = new String("world");
String s3 = String.valueOf(123); // 数字转字符串

// 基本属性
int len = s.length();           // 长度
boolean empty = s.isEmpty();    // 是否为空
char ch = s.charAt(0);          // 获取字符

// 查找
int index = s.indexOf("ll");    // 查找子串位置
int lastIndex = s.lastIndexOf("l"); // 最后出现位置
boolean contains = s.contains("el"); // 是否包含

// 截取
String sub = s.substring(1, 4); // [1, 4) 截取
String sub2 = s.substring(2);   // 从索引2到结尾

// 替换
String replaced = s.replace("l", "L"); // 替换所有
String replaced2 = s.replaceFirst("l", "L"); // 替换第一个
String replaced3 = s.replaceAll("[aeiou]", "*"); // 正则替换

// 分割
String[] parts = "a,b,c".split(","); // 按分隔符分割
String[] parts2 = "a b  c".split("\\s+"); // 按空格分割

// 拼接
String joined = String.join(",", "a", "b", "c"); // "a,b,c"
String concat = s.concat(" world"); // 拼接

// 大小写
String upper = s.toUpperCase();
String lower = s.toUpperCase().toLowerCase();

// 去空格
String trimmed = "  hello  ".trim(); // 去两端空格
String stripped = "  hello  ".strip(); // Java 11+

// 比较
boolean equals = s.equals("hello");
boolean equalsIgnoreCase = s.equalsIgnoreCase("HELLO");
int compare = s.compareTo("world"); // 字典序比较

// 判断
boolean startsWith = s.startsWith("he");
boolean endsWith = s.endsWith("lo");

// 转换
char[] chars = s.toCharArray(); // 转字符数组
byte[] bytes = s.getBytes();    // 转字节数组

StringBuilder - 可变字符串

使用场景:单线程环境下需要频繁修改字符串

// 初始化
StringBuilder sb = new StringBuilder();
StringBuilder sb2 = new StringBuilder("hello");
StringBuilder sb3 = new StringBuilder(100); // 指定初始容量

// 添加(拼接)
sb.append("hello");         // 尾部添加
sb.append(123);             // 添加数字
sb.append('!');             // 添加字符
sb.insert(0, "start ");     // 指定位置插入

// 删除
sb.delete(0, 5);            // 删除 [0, 5)
sb.deleteCharAt(0);         // 删除指定位置
sb.setLength(0);            // 清空(重置长度为0)

// 修改
sb.replace(0, 5, "world");  // 替换 [0, 5)
sb.setCharAt(0, 'H');       // 修改指定位置字符

// 反转
sb.reverse();

// 查询
int len = sb.length();
char ch = sb.charAt(0);
String sub = sb.substring(0, 5);

// 转换为 String
String result = sb.toString();

// 常见应用:循环拼接字符串
StringBuilder result = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    result.append(i).append(",");
}
// 比用 String + 拼接快得多!

StringBuffer - 可变字符串

使用场景:多线程环境下需要频繁修改字符串

// API 与 StringBuilder 完全相同
StringBuffer sb = new StringBuffer();
sb.append("hello");
sb.append(" world");
String result = sb.toString();

// 区别:StringBuffer 的方法都是 synchronized 的
// 多线程安全,但性能比 StringBuilder 差

常用字符串操作技巧

// 1. 字符串转数字
int num = Integer.parseInt("123");
long l = Long.parseLong("123");
double d = Double.parseDouble("3.14");

// 2. 数字转字符串
String s1 = String.valueOf(123);
String s2 = Integer.toString(123);
String s3 = "" + 123; // 不推荐

// 3. 字符串数组拼接
String[] arr = {"a", "b", "c"};
String joined = String.join(",", arr); // "a,b,c"

// 4. 重复字符串(Java 11+)
String repeated = "ab".repeat(3); // "ababab"

// 5. 判断空字符串
if (s != null && !s.isEmpty()) { }
if (s != null && !s.isBlank()) { } // Java 11+,忽略空白字符

// 6. 字符串格式化
String formatted = String.format("Hello %s, you are %d years old", "Alice", 25);

1. List - 列表

ArrayList - 动态数组

// 初始化
List<Integer> list = new ArrayList<>();
List<String> list2 = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<Integer> list3 = new ArrayList<>(100); // 指定初始容量

// 增
list.add(1);                    // 尾部添加
list.add(0, 10);                // 指定位置添加
list.addAll(Arrays.asList(2,3,4)); // 批量添加

// 删
list.remove(0);                 // 删除指定索引
list.remove(Integer.valueOf(1)); // 删除指定元素
list.clear();                   // 清空

// 改
list.set(0, 100);               // 修改指定位置

// 查
int val = list.get(0);          // 获取元素
int size = list.size();         // 获取大小
boolean empty = list.isEmpty(); // 是否为空
boolean contains = list.contains(1); // 是否包含
int index = list.indexOf(1);    // 查找索引

// 遍历
for (int num : list) {
    System.out.println(num);
}
// 或使用迭代器
Iterator<Integer> it = list.iterator();
while (it.hasNext()) {
    System.out.println(it.next());
}

LinkedList - 双向链表

LinkedList<Integer> list = new LinkedList<>();

// 头部操作
list.addFirst(1);
list.removeFirst();
int first = list.getFirst();

// 尾部操作
list.addLast(2);
list.removeLast();
int last = list.getLast();

// 作为队列使用
list.offer(3);      // 入队
int head = list.poll(); // 出队

// 作为栈使用
list.push(4);       // 入栈
int top = list.pop(); // 出栈

2. Set - 集合

HashSet - 无序不重复

// 初始化
Set<Integer> set = new HashSet<>();
Set<String> set2 = new HashSet<>(Arrays.asList("a", "b", "c"));

// 增
set.add(1);
set.addAll(Arrays.asList(2, 3, 4));

// 删
set.remove(1);
set.clear();

// 查
boolean contains = set.contains(1);
int size = set.size();
boolean empty = set.isEmpty();

// 遍历
for (int num : set) {
    System.out.println(num);
}

TreeSet - 有序集合(红黑树)

TreeSet<Integer> set = new TreeSet<>();

// 基本操作同 HashSet
set.add(3);
set.add(1);
set.add(2);
System.out.println(set); // [1, 2, 3] 自动排序

// 特有方法
int first = set.first();        // 最小元素
int last = set.last();          // 最大元素
int lower = set.lower(2);       // 小于 2 的最大元素
int higher = set.higher(2);     // 大于 2 的最小元素
int floor = set.floor(2);       // ≤ 2 的最大元素
int ceiling = set.ceiling(2);   // ≥ 2 的最小元素

// 子集操作
SortedSet<Integer> subset = set.subSet(1, 3); // [1, 3)
SortedSet<Integer> headSet = set.headSet(2);  // < 2
SortedSet<Integer> tailSet = set.tailSet(2);  // >= 2

LinkedHashSet - 保持插入顺序

Set<Integer> set = new LinkedHashSet<>();
set.add(3);
set.add(1);
set.add(2);
System.out.println(set); // [3, 1, 2] 保持插入顺序

3. Map - 映射

HashMap - 键值对

// 初始化
Map<String, Integer> map = new HashMap<>();

// 增/改
map.put("apple", 1);
map.put("banana", 2);
map.putIfAbsent("apple", 10); // 键不存在时才添加

// 删
map.remove("apple");
map.clear();

// 查
int val = map.get("apple");
int valOrDefault = map.getOrDefault("orange", 0);
boolean hasKey = map.containsKey("apple");
boolean hasValue = map.containsValue(1);
int size = map.size();

// 遍历键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
    String key = entry.getKey();
    int value = entry.getValue();
}

// 遍历键
for (String key : map.keySet()) {
    System.out.println(key);
}

// 遍历值
for (int value : map.values()) {
    System.out.println(value);
}

// Java 8+ 新方法
map.computeIfAbsent("cherry", k -> 3);
map.merge("apple", 1, Integer::sum); // 累加

TreeMap - 有序映射(红黑树)

TreeMap<Integer, String> map = new TreeMap<>();
map.put(3, "c");
map.put(1, "a");
map.put(2, "b");

// 基本操作同 HashMap
// 特有方法
int firstKey = map.firstKey();
int lastKey = map.lastKey();
Map.Entry<Integer, String> firstEntry = map.firstEntry();
Map.Entry<Integer, String> lastEntry = map.lastEntry();

int lowerKey = map.lowerKey(2);   // < 2 的最大键
int higherKey = map.higherKey(2); // > 2 的最小键
int floorKey = map.floorKey(2);   // <= 2 的最大键
int ceilingKey = map.ceilingKey(2); // >= 2 的最小键

LinkedHashMap - 保持插入顺序

Map<String, Integer> map = new LinkedHashMap<>();
map.put("c", 3);
map.put("a", 1);
map.put("b", 2);
// 遍历时保持插入顺序: c, a, b

4. Queue - 队列

普通队列

Queue<Integer> queue = new LinkedList<>();

// 入队
queue.offer(1);
queue.add(2); // 队列满时抛异常

// 出队
int head = queue.poll();  // 空时返回 null
int head2 = queue.remove(); // 空时抛异常

// 查看队首
int peek = queue.peek();  // 空时返回 null
int peek2 = queue.element(); // 空时抛异常

PriorityQueue - 优先队列(堆)

// 小根堆(默认)
PriorityQueue<Integer> minHeap = new PriorityQueue<>();

// 大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>((a, b) -> b - a);

// 自定义对象
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);

minHeap.offer(3);
minHeap.offer(1);
minHeap.offer(2);

while (!minHeap.isEmpty()) {
    System.out.println(minHeap.poll()); // 1, 2, 3
}

Deque - 双端队列

Deque<Integer> deque = new ArrayDeque<>();

// ===== 头部操作(相当于队列的 front) =====

deque.offerFirst(1);   // 将 1 添加到 deque 的头部
deque.pollFirst();     // 移除并返回头部元素,如果为空返回 null
deque.peekFirst();     // 返回头部元素,但不删除,如果为空返回 null

// ===== 尾部操作(相当于队列的 rear) =====

deque.offerLast(2);    // 将 2 添加到 deque 的尾部
deque.pollLast();      // 移除并返回尾部元素,如果为空返回 null
deque.peekLast();      // 返回尾部元素,但不删除,如果为空返回 null

// ===== 作为栈使用(LIFO) =====
deque.push(3);         // 将 3 压入栈顶
int top = deque.pop();  // 弹出栈顶元素并返回

5. Stack - 栈(不推荐使用)

Stack<Integer> stack = new Stack<>();

stack.push(1);      // 入栈
int top = stack.pop();  // 出栈
int peek = stack.peek(); // 查看栈顶
boolean empty = stack.isEmpty();
int size = stack.size();

// 推荐使用 Deque 代替 Stack
Deque<Integer> stack2 = new ArrayDeque<>();

6.其他技巧

Arrays.sort - 排序

// ==================== Arrays.sort() ====================  
  
// 一维数组排序  
int[] nums = {5,3,1,4,2};  
Arrays.sort(nums); // 升序排序  
  
// 二维数组排序  
int[][] arr = {{1,3},{2,2},{1,2}};  
  
// 按第一个元素升序  
Arrays.sort(arr, (a,b) -> a[0] - b[0]);  
  
// 按第一个元素降序  
Arrays.sort(arr, (a,b) -> b[0] - a[0]);  
  
// 多条件排序  
Arrays.sort(arr, (a,b) ->  
a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]  
); // 先按a[0]排序,相同再按a[1]  
  
// 降序 + 升序组合  
Arrays.sort(arr, (a,b) ->  
b[0] == a[0] ? a[1] - b[1] : b[0] - a[0]  
);  
     
  
  
// ==================== Arrays 工具 ====================  
  
// 填充数组  
int[] arr2 = new int[5];  
Arrays.fill(arr2, -1);  
  
// 指定范围填充  
Arrays.fill(arr2, 1, 4, 9);  
  
// 复制数组  
int[] copy = Arrays.copyOf(arr2, arr2.length);

Arrays工具

// ==================== Arrays 工具 ====================  
  
// 填充数组  
int[] arr2 = new int[5];  
Arrays.fill(arr2, -1);  
  
// 指定范围填充  
Arrays.fill(arr2, 1, 4, 9);  
  
// 复制数组  
int[] copy = Arrays.copyOf(arr2, arr2.length);```
---

## 🧮 Part 2: 算法模板

### 1. 树的遍历

#### 二叉树定义

```java
class TreeNode {
    int val;           // 节点值
    TreeNode left;     // 左子节点
    TreeNode right;    // 右子节点
    TreeNode(int val) { this.val = val; }
}

Math - 数值操作

// ==================== Math ====================  
  
int max = Math.max(a, b); // 最大值  
int min = Math.min(a, b); // 最小值  
int abs = Math.abs(-10); // 绝对值  
double pow = Math.pow(2,3); // 幂运算 2^3  
double ceil = Math.ceil(3.2); // 向上取整  
double floor = Math.floor(3.8); // 向下取整  
long round = Math.round(3.6); // 四舍五入

🧮 Part 2: 算法模板

1. 树的遍历

二叉树定义

class TreeNode {
    int val;           // 节点值
    TreeNode left;     // 左子节点
    TreeNode right;    // 右子节点
    TreeNode(int val) { this.val = val; }
}

DFS - 深度优先遍历

前序遍历(根-左-右)

// 递归版本 - 最简洁直观
void preorder(TreeNode root) {
    if (root == null) return;              // 递归终止条件:空节点
    System.out.println(root.val);          // 1. 先访问根节点
    preorder(root.left);                   // 2. 再遍历左子树
    preorder(root.right);                  // 3. 最后遍历右子树
}

// 迭代版本 - 使用栈模拟递归
List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);                      // 根节点入栈
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();       // 弹出栈顶节点
        res.add(node.val);                 // 访问该节点
        // 关键:先压右子节点,再压左子节点
        // 这样出栈时左子节点先出(栈是后进先出)
        if (node.right != null) stack.push(node.right);
        if (node.left != null) stack.push(node.left);
    }
    return res;
}

中序遍历(左-根-右)

// 递归版本
void inorder(TreeNode root) {
    if (root == null) return;              // 递归终止条件
    inorder(root.left);                    // 1. 先遍历左子树
    System.out.println(root.val);          // 2. 再访问根节点
    inorder(root.right);                   // 3. 最后遍历右子树
}

// 迭代版本 - 一直向左走到底
List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    TreeNode curr = root;
    
    while (curr != null || !stack.isEmpty()) {
        // 第一步:一直向左走到底,沿途节点入栈
        while (curr != null) {
            stack.push(curr);
            curr = curr.left;
        }
        // 第二步:弹出栈顶(当前最左节点),访问它
        curr = stack.pop();
        res.add(curr.val);
        // 第三步:转向右子树
        curr = curr.right;
    }
    return res;
}

后序遍历(左-右-根)

// 递归版本
void postorder(TreeNode root) {
    if (root == null) return;              // 递归终止条件
    postorder(root.left);                  // 1. 先遍历左子树
    postorder(root.right);                 // 2. 再遍历右子树
    System.out.println(root.val);          // 3. 最后访问根节点
}

// 迭代版本 - 巧妙方法:前序遍历变形 + 反转
// 思路:前序是"根左右",改成"根右左",反转后得到"左右根"
List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> res = new ArrayList<>();
    if (root == null) return res;
    
    Stack<TreeNode> stack = new Stack<>();
    stack.push(root);
    
    while (!stack.isEmpty()) {
        TreeNode node = stack.pop();
        res.add(node.val);                 // 添加到结果(根右左顺序)
        // 注意:这里先左后右,出栈时就是先右后左
        if (node.left != null) stack.push(node.left);
        if (node.right != null) stack.push(node.right);
    }
    Collections.reverse(res);              // 反转得到左右根
    return res;
}

BFS - 层序遍历

// 按层遍历二叉树,每层的节点放在一个列表中
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;
}

线段树(Segment Tree)

/**
 * 线段树:用于高效处理区间查询和单点修改
 * 时间复杂度:构建 O(n),查询 O(log n),更新 O(log n)
 */
class SegmentTree {
    private int[] tree;    // 线段树数组
    private int n;         // 原数组大小
    
    public SegmentTree(int[] nums) {
        n = nums.length;
        tree = new int[4 * n];             // 线段树最多需要 4n 空间
        build(nums, 0, 0, n - 1);
    }
    
    // 构建线段树:递归构建
    // node: 当前节点在 tree 中的索引
    // start, end: 当前节点代表的区间 [start, end]
    private void build(int[] nums, int node, int start, int end) {
        if (start == end) {
            // 叶子节点:直接存储原数组的值
            tree[node] = nums[start];
            return;
        }
        int mid = start + (end - start) / 2;
        int leftNode = 2 * node + 1;       // 左子节点索引
        int rightNode = 2 * node + 2;      // 右子节点索引
        
        // 递归构建左右子树
        build(nums, leftNode, start, mid);
        build(nums, rightNode, mid + 1, end);
        // 当前节点的值 = 左子树 + 右子树(区间和)
        tree[node] = tree[leftNode] + tree[rightNode];
    }
    
    // 区间查询:查询区间 [l, r] 的和
    public int query(int l, int r) {
        return query(0, 0, n - 1, l, r);
    }
    
    private int query(int node, int start, int end, int l, int r) {
        // 情况1:当前区间与查询区间完全不相交
        if (l > end || r < start) return 0;
        // 情况2:当前区间完全包含在查询区间内
        if (l <= start && end <= r) return tree[node];
        
        // 情况3:部分相交,需要递归查询左右子树
        int mid = start + (end - start) / 2;
        int leftSum = query(2 * node + 1, start, mid, l, r);
        int rightSum = query(2 * node + 2, mid + 1, end, l, r);
        return leftSum + rightSum;
    }
    
    // 单点更新:将 index 位置的值更新为 val
    public void update(int index, int val) {
        update(0, 0, n - 1, index, val);
    }
    
    private void update(int node, int start, int end, int index, int val) {
        if (start == end) {
            // 找到叶子节点,更新值
            tree[node] = val;
            return;
        }
        int mid = start + (end - start) / 2;
        int leftNode = 2 * node + 1;
        int rightNode = 2 * node + 2;
        
        // 判断 index 在左子树还是右子树
        if (index <= mid) {
            update(leftNode, start, mid, index, val);
        } else {
            update(rightNode, mid + 1, end, index, val);
        }
        // 更新完子树后,更新当前节点
        tree[node] = tree[leftNode] + tree[rightNode];
    }
}

2. 图算法

图的表示

// 方式1:邻接表(适合稀疏图)
Map<Integer, List<Integer>> graph = new HashMap<>();
// 或使用 ArrayList
List<List<Integer>> graph = new ArrayList<>();

// 方式2:邻接矩阵(适合密集图)
int[][] graph = new int[n][n];  // graph[i][j] 表示 i 到 j 的边权

DFS - 图的深度优先遍历

// 递归实现 DFS
void dfs(int node, Set<Integer> visited, List<List<Integer>> graph) {
    visited.add(node);                     // 标记当前节点已访问
    System.out.println(node);              // 处理当前节点
    
    // 遍历所有邻居节点
    for (int neighbor : graph.get(node)) {
        if (!visited.contains(neighbor)) { // 如果邻居未访问
            dfs(neighbor, visited, graph); // 递归访问邻居
        }
    }
}

// 使用示例
Set<Integer> visited = new HashSet<>();
dfs(0, visited, graph);                    // 从节点 0 开始 DFS

BFS - 图的广度优先遍历

// 使用队列实现 BFS
void bfs(int start, List<List<Integer>> graph) {
    Set<Integer> visited = new HashSet<>();
    Queue<Integer> queue = new LinkedList<>();
    
    queue.offer(start);                    // 起始节点入队
    visited.add(start);                    // 标记已访问
    
    while (!queue.isEmpty()) {
        int node = queue.poll();           // 出队
        System.out.println(node);          // 处理当前节点
        
        // 遍历所有邻居节点
        for (int neighbor : graph.get(node)) {
            if (!visited.contains(neighbor)) {
                queue.offer(neighbor);     // 邻居入队
                visited.add(neighbor);     // 标记已访问
            }
        }
    }
}

拓扑排序(Kahn 算法)

image.png

/**
 * 拓扑排序:将有向无环图(DAG)转换为线性序列
 * 应用:课程安排、任务调度等
 * 核心思想:不断移除入度为0的节点
 */
List<Integer> topologicalSort(int n, int[][] edges) {
    // 构建邻接表
    List<List<Integer>> graph = new ArrayList<>();
    int[] inDegree = new int[n];          // 记录每个节点的入度
    
    for (int i = 0; i < n; i++) {
        graph.add(new ArrayList<>());
    }
    
    // 构建图并计算入度
    for (int[] edge : edges) {
        graph.get(edge[0]).add(edge[1]);  // edge[0] -> edge[1]
        inDegree[edge[1]]++;              // edge[1] 的入度+1
    }
    
    // 将所有入度为0的节点入队
    Queue<Integer> queue = new LinkedList<>();
    for (int i = 0; i < n; i++) {
        if (inDegree[i] == 0) {
            queue.offer(i);
        }
    }
    
    List<Integer> res = new ArrayList<>();
    while (!queue.isEmpty()) {
        int node = queue.poll();
        res.add(node);                     // 将节点加入结果
        
        // 删除该节点的所有出边
        for (int neighbor : graph.get(node)) {
            inDegree[neighbor]--;          // 邻居的入度-1
            if (inDegree[neighbor] == 0) { // 入度变为0,入队
                queue.offer(neighbor);
            }
        }
    }
    
    // 如果所有节点都被访问,说明无环,返回结果;否则返回空
    return res.size() == n ? res : new ArrayList<>();
}

Dijkstra 最短路径算法

/**
 * Dijkstra算法:单源最短路径(不能有负权边)
 * 时间复杂度:O(E log V),E是边数,V是顶点数
 * 核心思想:贪心,每次选择距离最小的未访问节点
 */
int[] dijkstra(int n, int[][] edges, int start) {
    // 构建邻接表: {neighbor, weight}
    List<List<int[]>> graph = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        graph.add(new ArrayList<>());
    }
    for (int[] edge : edges) {
        // edge = [from, to, weight]
        graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
    }
    
    // dist[i] 表示从 start 到 i 的最短距离
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[start] = 0;
    
    // 优先队列: {distance, node},按距离从小到大排序
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[0] - b[0]);
    pq.offer(new int[]{0, start});
    
    while (!pq.isEmpty()) {
        int[] curr = pq.poll();
        int d = curr[0], node = curr[1];
        
        // 如果当前距离大于已知最短距离,跳过
        if (d > dist[node]) continue;
        
        // 遍历所有邻居,尝试松弛操作
        for (int[] neighbor : graph.get(node)) {
            int next = neighbor[0], weight = neighbor[1];
            int newDist = d + weight;
            
            // 如果找到更短的路径,更新距离
            if (newDist < dist[next]) {
                dist[next] = newDist;
                pq.offer(new int[]{newDist, next});
            }
        }
    }
    
    return dist;
}

Bellman-Ford 算法(可处理负权边)

/**
 * Bellman-Ford算法:单源最短路径,可处理负权边
 * 时间复杂度:O(V * E)
 * 可以检测负环
 */
int[] bellmanFord(int n, int[][] edges, int start) {
    int[] dist = new int[n];
    Arrays.fill(dist, Integer.MAX_VALUE);
    dist[start] = 0;
    
    // 松弛操作:最多进行 n-1 次
    // 原理:最短路径最多包含 n-1 条边
    for (int i = 0; i < n - 1; i++) {
        for (int[] edge : edges) {
            int u = edge[0], v = edge[1], w = edge[2];
            // 松弛操作:如果经过 u 到 v 更短,则更新
            if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) {
                dist[v] = dist[u] + w;
            }
        }
    }
    
    // 第 n 次松弛:如果还能更新,说明存在负环
    for (int[] edge : edges) {
        int u = edge[0], v = edge[1], w = edge[2];
        if (dist[u] != Integer.MAX_VALUE && dist[u] + w < dist[v]) {
            return null;                   // 存在负环
        }
    }
    
    return dist;
}

Floyd-Warshall 全源最短路径

/**
 * Floyd-Warshall算法:计算所有点对之间的最短路径
 * 时间复杂度:O(V³)
 * 核心思想:动态规划,逐步加入中间节点
 */
int[][] floydWarshall(int n, int[][] edges) {
    int[][] dist = new int[n][n];
    
    // 初始化距离矩阵
    for (int i = 0; i < n; i++) {
        Arrays.fill(dist[i], Integer.MAX_VALUE / 2);  // 除以2防止溢出
        dist[i][i] = 0;                    // 自己到自己距离为0
    }
    
    // 填入边的权重
    for (int[] edge : edges) {
        dist[edge[0]][edge[1]] = edge[2];
    }
    
    // 动态规划:k 是中间节点
    // dist[i][j] = min(dist[i][j], dist[i][k] + dist[k][j])
    for (int k = 0; k < n; k++) {          // 枚举中间节点
        for (int i = 0; i < n; i++) {      // 枚举起点
            for (int j = 0; j < n; j++) {  // 枚举终点
                // 如果经过 k 中转更短,则更新
                dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j]);
            }
        }
    }
    
    return dist;
}

Prim 最小生成树

/**
 * Prim算法:构建最小生成树
 * 时间复杂度:O(E log V)
 * 核心思想:从一个节点开始,逐步扩展,每次选最小边
 */
int prim(int n, int[][] edges) {
    // 构建邻接表
    List<List<int[]>> graph = new ArrayList<>();
    for (int i = 0; i < n; i++) {
        graph.add(new ArrayList<>());
    }
    for (int[] edge : edges) {
        // 无向图:添加双向边
        graph.get(edge[0]).add(new int[]{edge[1], edge[2]});
        graph.get(edge[1]).add(new int[]{edge[0], edge[2]});
    }
    
    boolean[] visited = new boolean[n];
    // 优先队列:{node, weight},按权重从小到大
    PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> a[1] - b[1]);
    pq.offer(new int[]{0, 0});             // 从节点0开始,权重0
    
    int totalWeight = 0;                   // 最小生成树的总权重
    int edgeCount = 0;                     // 已添加的边数
    
    while (!pq.isEmpty() && edgeCount < n) {
        int[] curr = pq.poll();
        int node = curr[0], weight = curr[1];
        
        if (visited[node]) continue;       // 已访问,跳过
        visited[node] = true;
        totalWeight += weight;             // 加入最小生成树
        edgeCount++;
        
        // 将邻居节点的边加入优先队列
        for (int[] neighbor : graph.get(node)) {
            if (!visited[neighbor[0]]) {
                pq.offer(neighbor);
            }
        }
    }
    
    // 如果所有节点都连通,返回总权重;否则返回-1
    return edgeCount == n ? totalWeight : -1;
}

Kruskal 最小生成树(并查集)

/**
 * 并查集:用于判断连通性和合并集合
 */
class UnionFind {
    int[] parent;      // parent[i] 表示 i 的父节点
    int[] rank;        // rank[i] 表示以 i 为根的树的高度
    
    public UnionFind(int n) {
        parent = new int[n];
        rank = new int[n];
        for (int i = 0; i < n; i++) {
            parent[i] = i;                 // 初始时每个节点的父节点是自己
        }
    }
    
    // 查找:找到 x 所在集合的代表元素(路径压缩)
    public int find(int x) {
        if (parent[x] != x) {
            parent[x] = find(parent[x]);   // 路径压缩:直接连到根节点
        }
        return parent[x];
    }
    
    // 合并:将 x 和 y 所在的集合合并(按秩合并)
    public boolean union(int x, int y) {
        int rootX = find(x), rootY = find(y);
        if (rootX == rootY) return false;  // 已经在同一集合
        
        // 按秩合并:将矮树挂到高树上
        if (rank[rootX] < rank[rootY]) {
            parent[rootX] = rootY;
        } else if (rank[rootX] > rank[rootY]) {
            parent[rootY] = rootX;
        } else {
            parent[rootY] = rootX;
            rank[rootX]++;                 // 高度相同,随便挂,高度+1
        }
        return true;
    }
}

/**
 * Kruskal算法:构建最小生成树
 * 时间复杂度:O(E log E)
 * 核心思想:按边权从小到大排序,用并查集避免成环
 */
int kruskal(int n, int[][] edges) {
    // 按边权从小到大排序
    Arrays.sort(edges, (a, b) -> a[2] - b[2]);
    UnionFind uf = new UnionFind(n);
    
    int totalWeight = 0;
    int edgeCount = 0;
    
    for (int[] edge : edges) {
        // 如果两个节点不在同一集合,添加这条边
        if (uf.union(edge[0], edge[1])) {
            totalWeight += edge[2];
            edgeCount++;
            if (edgeCount == n - 1) break; // 最小生成树有 n-1 条边
        }
    }
    
    return edgeCount == n - 1 ? totalWeight : -1;
}

3. 回溯算法

组合问题

/**
 * 组合问题:从 n 个数中选 k 个数的所有组合
 * 例如:n=4, k=2 -> [[1,2],[1,3],[1,4],[2,3],[2,4],[3,4]]
 */
List<List<Integer>> combine(int n, int k) {
    List<List<Integer>> res = new ArrayList<>();
    backtrack(res, new ArrayList<>(), 1, n, k);
    return res;
}

void backtrack(List<List<Integer>> res, List<Integer> path, int start, int n, int k) {
    // 递归终止条件:已选择 k 个数
    if (path.size() == k) {
        res.add(new ArrayList<>(path));    // 注意:要复制一份
        return;
    }
    
    // 从 start 开始枚举,避免重复
    for (int i = start; i <= n; i++) {
        path.add(i);                       // 做选择
        backtrack(res, path, i + 1, n, k); // 递归
        path.remove(path.size() - 1);      // 撤销选择(回溯)
    }
}

全排列

/**
 * 全排列问题:给定数组,返回所有可能的排列
 * 例如:[1,2,3] -> [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
 */
List<List<Integer>> permute(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    backtrack(res, new ArrayList<>(), nums, new boolean[nums.length]);
    return res;
}

void backtrack(List<List<Integer>> res, List<Integer> path, int[] nums, boolean[] used) {
    // 递归终止条件:所有数字都已使用
    if (path.size() == nums.length) {
        res.add(new ArrayList<>(path));
        return;
    }
    
    // 枚举所有数字
    for (int i = 0; i < nums.length; i++) {
        if (used[i]) continue;             // 已使用过,跳过
        
        path.add(nums[i]);                 // 做选择
        used[i] = true;                    // 标记已使用
        backtrack(res, path, nums, used);  // 递归
        used[i] = false;                   // 撤销标记(回溯)
        path.remove(path.size() - 1);      // 撤销选择(回溯)
    }
}

子集问题

/**
 * 子集问题:返回数组的所有子集(幂集)
 * 例如:[1,2,3] -> [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
 */
List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    backtrack(res, new ArrayList<>(), nums, 0);
    return res;
}

void backtrack(List<List<Integer>> res, List<Integer> path, int[] nums, int start) {
    // 每个状态都是一个子集
    res.add(new ArrayList<>(path));
    
    // 枚举后续元素
    for (int i = start; i < nums.length; i++) {
        path.add(nums[i]);                 // 做选择
        backtrack(res, path, nums, i + 1); // 递归
        path.remove(path.size() - 1);      // 撤销选择(回溯)
    }
}

N 皇后问题

/**
 * N皇后问题:在 n×n 的棋盘上放置 n 个皇后,使它们互不攻击
 * 规则:任意两个皇后不能在同一行、同一列、同一对角线
 */
List<List<String>> solveNQueens(int n) {
    List<List<String>> res = new ArrayList<>();
    char[][] board = new char[n][n];
    // 初始化棋盘:'.' 表示空位
    for (int i = 0; i < n; i++) {
        Arrays.fill(board[i], '.');
    }
    backtrack(res, board, 0);
    return res;
}

void backtrack(List<List<String>> res, char[][] board, int row) {
    // 递归终止条件:所有行都放置完成
    if (row == board.length) {
        res.add(construct(board));         // 将棋盘转换为字符串列表
        return;
    }
    
    // 尝试在当前行的每一列放置皇后
    for (int col = 0; col < board.length; col++) {
        if (!isValid(board, row, col)) continue;  // 不合法,跳过
        
        board[row][col] = 'Q';             // 放置皇后
        backtrack(res, board, row + 1);    // 递归下一行
        board[row][col] = '.';             // 撤销放置(回溯)
    }
}

// 检查在 (row, col) 位置放置皇后是否合法
boolean isValid(char[][] board, int row, int col) {
    int n = board.length;
    
    // 检查列:同一列不能有其他皇后
    for (int i = 0; i < row; i++) {
        if (board[i][col] == 'Q') return false;
    }
    
    // 检查左上对角线
    for (int i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) {
        if (board[i][j] == 'Q') return false;
    }
    
    // 检查右上对角线
    for (int i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) {
        if (board[i][j] == 'Q') return false;
    }
    
    return true;
}

// 将棋盘转换为字符串列表
List<String> construct(char[][] board) {
    List<String> res = new ArrayList<>();
    for (char[] row : board) {
        res.add(new String(row));
    }
    return res;
}

4. 动态规划

0-1 背包问题

/**
 * 0-1背包:每个物品只能选一次
 * weights[i]: 第i个物品的重量
 * values[i]: 第i个物品的价值
 * capacity: 背包容量
 * 返回:最大价值
 */
int knapsack(int[] weights, int[] values, int capacity) {
    int n = weights.length;
    // dp[i][w] 表示前 i 个物品,背包容量为 w 时的最大价值
    int[][] dp = new int[n + 1][capacity + 1];
    
    for (int i = 1; i <= n; i++) {
        for (int w = 0; w <= capacity; w++) {
            // 如果当前物品的重量 <= 背包容量
            if (weights[i - 1] <= w) {
                // 选择:max(不拿, 拿)
                dp[i][w] = Math.max(
                    dp[i - 1][w],                              // 不拿第i个物品
                    dp[i - 1][w - weights[i - 1]] + values[i - 1]  // 拿第i个物品
                );
            } else {
                // 放不下,只能不拿
                dp[i][w] = dp[i - 1][w];
            }
        }
    }
    
    return dp[n][capacity];
}

/**
 * 0-1背包 - 空间优化版本
 * 时间复杂度:O(n * capacity)
 * 空间复杂度:O(capacity)
 */
int knapsackOptimized(int[] weights, int[] values, int capacity) {
    int[] dp = new int[capacity + 1];
    
    for (int i = 0; i < weights.length; i++) {
        // 注意:必须从后往前遍历,避免重复使用同一物品
        for (int w = capacity; w >= weights[i]; w--) {
            dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }
    
    return dp[capacity];
}

完全背包问题

/**
 * 完全背包:每个物品可以选无限次
 * 与0-1背包的区别:内层循环从前往后遍历
 */
int completeKnapsack(int[] weights, int[] values, int capacity) {
    int[] dp = new int[capacity + 1];
    
    for (int i = 0; i < weights.length; i++) {
        // 注意:从前往后遍历,允许重复使用物品
        for (int w = weights[i]; w <= capacity; w++) {
            dp[w] = Math.max(dp[w], dp[w - weights[i]] + values[i]);
        }
    }
    
    return dp[capacity];
}

最长公共子序列(LCS)

/**
 * 最长公共子序列:找两个字符串的最长公共子序列长度
 * 子序列:不要求连续
 * 例如:"abcde" 和 "ace" 的LCS是 "ace",长度为3
 */
int longestCommonSubsequence(String text1, String text2) {
    int m = text1.length(), n = text2.length();
    // dp[i][j] 表示 text1[0..i-1] 和 text2[0..j-1] 的LCS长度
    int[][] dp = new int[m + 1][n + 1];
    
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (text1.charAt(i - 1) == text2.charAt(j - 1)) {
                // 字符相同:LCS长度+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];
}

最长递增子序列(LIS)

/**
 * 最长递增子序列 - O(n²) 动态规划解法
 * dp[i] 表示以 nums[i] 结尾的最长递增子序列长度
 */
int lengthOfLIS(int[] nums) {
    int n = nums.length;
    int[] dp = new int[n];
    Arrays.fill(dp, 1);                    // 初始每个元素自己构成长度为1的子序列
    
    int maxLen = 1;
    for (int i = 1; i < n; i++) {
        for (int j = 0; j < i; j++) {
            // 如果 nums[i] 可以接在 nums[j] 后面
            if (nums[i] > nums[j]) {
                dp[i] = Math.max(dp[i], dp[j] + 1);
            }
        }
        maxLen = Math.max(maxLen, dp[i]);
    }
    
    return maxLen;
}

/**
 * 最长递增子序列 - O(n log n) 二分解法
 * tails[i] 表示长度为 i+1 的递增子序列的最小尾部元素
 */
int lengthOfLISBinary(int[] nums) {
    List<Integer> tails = new ArrayList<>();
    
    for (int num : nums) {
        // 二分查找:找到第一个 >= num 的位置
        int left = 0, right = tails.size();
        while (left < right) {
            int mid = left + (right - left) / 2;
            if (tails.get(mid) < num) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        
        // 如果 num 比所有元素都大,追加到末尾
        if (left == tails.size()) {
            tails.add(num);
        } else {
            // 否则替换找到的位置
            tails.set(left, num);
        }
    }
    
    return tails.size();
}

编辑距离

/**
 * 编辑距离:将 word1 转换为 word2 的最少操作次数
 * 操作:插入、删除、替换
 * 例如:"horse" -> "ros" 需要3步(删除h、删除r、替换s)
 */
int minDistance(String word1, String word2) {
    int m = word1.length(), n = word2.length();
    // dp[i][j] 表示 word1[0..i-1] 转换为 word2[0..j-1] 的最少操作数
    int[][] dp = new int[m + 1][n + 1];
    
    // 边界条件:一个字符串为空
    for (int i = 0; i <= m; i++) dp[i][0] = i;  // word1 -> 空串:删除i个字符
    for (int j = 0; j <= n; j++) dp[0][j] = j;  // 空串 -> word2:插入j个字符
    
    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= n; j++) {
            if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
                // 字符相同:不需要操作
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 字符不同:三种操作取最小
                dp[i][j] = Math.min(
                    Math.min(
                        dp[i - 1][j],      // 删除 word1[i-1]
                        dp[i][j - 1]       // 插入 word2[j-1]
                    ),
                    dp[i - 1][j - 1]       // 替换 word1[i-1] 为 word2[j-1]
                ) + 1;
            }
        }
    }
    
    return dp[m][n];
}

股票买卖问题

只能买卖一次

/**
 * 买卖股票的最佳时机 I:只能买卖一次
 * 贪心思想:记录最低价格,计算每天卖出的最大利润
 */
int maxProfit(int[] prices) {
    int minPrice = Integer.MAX_VALUE;      // 记录目前为止的最低价格
    int maxProfit = 0;                     // 记录最大利润
    
    for (int price : prices) {
        minPrice = Math.min(minPrice, price);
        maxProfit = Math.max(maxProfit, price - minPrice);
    }
    
    return maxProfit;
}

可以买卖多次

/**
 * 买卖股票的最佳时机 II:可以买卖多次
 * 贪心思想:只要今天价格比昨天高,就在昨天买今天卖
 */
int maxProfitMultiple(int[] prices) {
    int profit = 0;
    
    for (int i = 1; i < prices.length; i++) {
        // 如果今天价格更高,累加差价
        if (prices[i] > prices[i - 1]) {
            profit += prices[i] - prices[i - 1];
        }
    }
    
    return profit;
}

最多买卖 k 次

/**
 * 买卖股票的最佳时机 III/IV:最多买卖 k 次
 * dp[i][j] 表示第 i 次交易后,第 j 天的最大利润
 */
int maxProfitK(int k, int[] prices) {
    if (prices.length == 0) return 0;
    
    int n = prices.length;
    // 如果 k >= n/2,相当于无限次交易
    if (k >= n / 2) {
        int profit = 0;
        for (int i = 1; i < n; i++) {
            if (prices[i] > prices[i - 1]) {
                profit += prices[i] - prices[i - 1];
            }
        }
        return profit;
    }
    
    int[][] dp = new int[k + 1][n];
    
    for (int i = 1; i <= k; i++) {
        int maxDiff = -prices[0];          // 第 i-1 次交易后买入的最大收益
        for (int j = 1; j < n; j++) {
            // 不操作 vs 卖出
            dp[i][j] = Math.max(dp[i][j - 1], prices[j] + maxDiff);
            // 更新买入的最大收益
            maxDiff = Math.max(maxDiff, dp[i - 1][j] - prices[j]);
        }
    }
    
    return dp[k][n - 1];
}

打家劫舍

/**
 * 打家劫舍 I:线性排列的房屋
 * 不能抢相邻的房屋
 * dp[i] = max(dp[i-1], dp[i-2] + nums[i])
 */
int rob(int[] nums) {
    if (nums.length == 0) return 0;
    if (nums.length == 1) return nums[0];
    
    int prev2 = 0, prev1 = 0;              // prev2: dp[i-2], prev1: dp[i-1]
    for (int num : nums) {
        int temp = prev1;
        // 不抢 vs 抢
        prev1 = Math.max(prev1, prev2 + num);
        prev2 = temp;
    }
    
    return prev1;
}

/**
 * 打家劫舍 II:环形排列的房屋
 * 第一个和最后一个房屋相邻
 * 思路:分两种情况,取最大值
 * 1. 抢第一个房屋,不抢最后一个
 * 2. 不抢第一个房屋,抢最后一个
 */
int robCircular(int[] nums) {
    if (nums.length == 1) return nums[0];
    return Math.max(
        robRange(nums, 0, nums.length - 2),
        robRange(nums, 1, nums.length - 1)
    );
}

int robRange(int[] nums, int start, int end) {
    int prev2 = 0, prev1 = 0;
    for (int i = start; i <= end; i++) {
        int temp = prev1;
        prev1 = Math.max(prev1, prev2 + nums[i]);
        prev2 = temp;
    }
    return prev1;
}

分割等和子集

/**
 * 分割等和子集:判断是否能将数组分成两个和相等的子集
 * 本质:0-1背包问题,目标是找到和为 sum/2 的子集
 */
boolean canPartition(int[] nums) {
    int sum = 0;
    for (int num : nums) sum += num;
    
    if (sum % 2 != 0) return false;        // 和为奇数,不可能分割
    
    int target = sum / 2;
    // dp[j] 表示能否凑出和为 j
    boolean[] dp = new boolean[target + 1];
    dp[0] = true;                          // 和为0总是可以(不选任何数)
    
    for (int num : nums) {
        // 从后往前遍历,避免重复使用
        for (int j = target; j >= num; j--) {
            dp[j] = dp[j] || dp[j - num]; // 不选 num 或 选 num
        }
    }
    
    return dp[target];
}

零钱兑换

/**
 * 零钱兑换 I:最少硬币数
 * dp[i] 表示凑出金额 i 所需的最少硬币数
 */
int coinChange(int[] coins, int amount) {
    int[] dp = new int[amount + 1];
    Arrays.fill(dp, amount + 1);           // 初始化为不可能的大值
    dp[0] = 0;                             // 凑出0元需要0个硬币
    
    for (int i = 1; i <= amount; i++) {
        for (int coin : coins) {
            if (i >= coin) {
                // 选择使用当前硬币
                dp[i] = Math.min(dp[i], dp[i - coin] + 1);
            }
        }
    }
    
    return dp[amount] > amount ? -1 : dp[amount];
}

/**
 * 零钱兑换 II:组成方案数
 * dp[i] 表示凑出金额 i 的方案数
 */
int change(int amount, int[] coins) {
    int[] dp = new int[amount + 1];
    dp[0] = 1;                             // 凑出0元有1种方案(不选)
    
    // 外层遍历硬币,内层遍历金额(避免重复计数)
    for (int coin : coins) {
        for (int i = coin; i <= amount; i++) {
            dp[i] += dp[i - coin];
        }
    }
    
    return dp[amount];
}

5. 双指针

对撞指针

// 两数之和(有序数组)
int[] twoSum(int[] nums, int target) {
    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--;
        }
    }
    
    return new int[]{-1, -1};
}

// 三数之和
List<List<Integer>> threeSum(int[] nums) {
    List<List<Integer>> res = new ArrayList<>();
    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;
}

快慢指针

// 链表中点
ListNode findMiddle(ListNode head) {
    ListNode slow = head, fast = head;
    
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
    }
    
    return slow;
}

// 环形链表
boolean hasCycle(ListNode head) {
    if (head == null) return false;
    
    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;
}

// 找环的入口
ListNode detectCycle(ListNode head) {
    ListNode slow = head, fast = head;
    
    while (fast != null && fast.next != null) {
        slow = slow.next;
        fast = fast.next.next;
        
        if (slow == fast) {
            slow = head;
            while (slow != fast) {
                slow = slow.next;
                fast = fast.next;
            }
            return slow;
        }
    }
    
    return null;
}

6. 单调栈

// 下一个更大元素
int[] nextGreaterElement(int[] nums) {
    int n = nums.length;
    int[] res = new int[n];
    Arrays.fill(res, -1);
    Stack<Integer> stack = new Stack<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && nums[stack.peek()] < nums[i]) {
            int idx = stack.pop();
            res[idx] = nums[i];
        }
        stack.push(i);
    }
    
    return res;
}

// 每日温度
int[] dailyTemperatures(int[] temperatures) {
    int n = temperatures.length;
    int[] res = new int[n];
    Stack<Integer> stack = new Stack<>();
    
    for (int i = 0; i < n; i++) {
        while (!stack.isEmpty() && temperatures[stack.peek()] < temperatures[i]) {
            int idx = stack.pop();
            res[idx] = i - idx;
        }
        stack.push(i);
    }
    
    return res;
}

// 柱状图中最大的矩形
int largestRectangleArea(int[] heights) {
    Stack<Integer> stack = new Stack<>();
    stack.push(-1);
    int maxArea = 0;
    
    for (int i = 0; i < heights.length; i++) {
        while (stack.peek() != -1 && heights[stack.peek()] >= heights[i]) {
            int h = heights[stack.pop()];
            int w = i - stack.peek() - 1;
            maxArea = Math.max(maxArea, h * w);
        }
        stack.push(i);
    }
    
    while (stack.peek() != -1) {
        int h = heights[stack.pop()];
        int w = heights.length - stack.peek() - 1;
        maxArea = Math.max(maxArea, h * w);
    }
    
    return maxArea;
}

7. 前缀和

// 一维前缀和
class PrefixSum {
    private int[] preSum;
    
    public PrefixSum(int[] nums) {
        preSum = new int[nums.length + 1];
        for (int i = 0; i < nums.length; i++) {
            preSum[i + 1] = preSum[i] + nums[i];
        }
    }
    
    // 查询区间 [left, right] 的和
    public int query(int left, int right) {
        return preSum[right + 1] - preSum[left];
    }
}

// 二维前缀和
class MatrixPrefixSum {
    private int[][] preSum;
    
    public MatrixPrefixSum(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
        preSum = new int[m + 1][n + 1];
        
        for (int i = 1; i <= m; i++) {
            for (int j = 1; j <= n; j++) {
                preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] 
                             - preSum[i - 1][j - 1] + matrix[i - 1][j - 1];
            }
        }
    }
    
    // 查询子矩阵 [row1, col1] 到 [row2, col2] 的和
    public int query(int row1, int col1, int row2, int col2) {
        return preSum[row2 + 1][col2 + 1] 
             - preSum[row1][col2 + 1] 
             - preSum[row2 + 1][col1] 
             + preSum[row1][col1];
    }
}

📌 总结

这份文档涵盖了:

  • Java 集合框架:List、Set、Map、Queue、Stack 的完整使用方法
  • 树算法:DFS、BFS、线段树
  • 图算法:遍历、最短路径、最小生成树
  • 经典算法:回溯、动态规划、二分查找、滑动窗口、双指针、单调栈、前缀和

建议配合 LeetCode 刷题使用,遇到问题时可以快速查阅对应的模板代码。

持续更新中… 🚀