Java算法
Java 中 Deque 的详解(模拟栈和队列)
1. 什么是 Deque
Deque 是 Double Ended Queue(双端队列) 的缩写,是 Java 集合框架中的接口之一。特点:
- 可以在 队头(front)和队尾(rear) 同时插入和删除元素。
- 可以用作:
- 队列(FIFO):先进先出
- 栈(LIFO):后进先出
常用实现类:
ArrayDeque(数组实现,速度快,不允许null)LinkedList(链表实现,允许null)
2. 核心方法
2.1 插入元素
| 方法 | 功能 | 异常/返回值 |
|---|---|---|
addFirst(E e) |
队头插入元素 | 队列满时抛异常 |
addLast(E e) |
队尾插入元素 | 队列满时抛异常 |
offerFirst(E e) |
队头插入元素 | 队列满返回 false |
offerLast(E e) |
队尾插入元素 | 队列满返回 false |
2.2 删除元素
| 方法 | 功能 | 异常/返回值 |
|---|---|---|
removeFirst() |
删除并返回队头元素 | 空队列抛异常 |
removeLast() |
删除并返回队尾元素 | 空队列抛异常 |
pollFirst() |
删除并返回队头元素 | 空队列返回 null |
pollLast() |
删除并返回队尾元素 | 空队列返回 null |
2.3 查看元素(不删除)
| 方法 | 功能 | 异常/返回值 |
|---|---|---|
getFirst() |
获取队头元素 | 空队列抛异常 |
getLast() |
获取队尾元素 | 空队列抛异常 |
peekFirst() |
获取队头元素 | 空队列返回 null |
peekLast() |
获取队尾元素 | 空队列返回 null |
2.4 栈操作方法(LIFO)
| 方法 | 功能 |
|---|---|
push(E e) |
将元素压入栈顶(相当于 addFirst) |
pop() |
弹出栈顶元素(相当于 removeFirst) |
peek() |
查看栈顶元素(相当于 peekFirst) |
2.5获取队列长度
deque.size();
3. 常见实现类
3.1 ArrayDeque
Deque<Integer> deque = new ArrayDeque<>();
特点:
- 内部基于数组实现
- 不允许
null - 比
LinkedList更快(无额外链表开销) - 动态扩容
3.2 LinkedList
Deque<String> deque = new LinkedList<>();
特点:
- 双向链表实现
- 允许
null - 支持队列和栈操作
4. 使用示例
4.1 双端队列操作
import java.util.ArrayDeque;
import java.util.Deque;
public class DequeExample {
public static void main(String[] args) {
Deque<Integer> deque = new ArrayDeque<>();
// 插入元素
deque.addFirst(1); // 1
deque.addLast(2); // 1 2
deque.offerFirst(0); // 0 1 2
deque.offerLast(3); // 0 1 2 3
System.out.println("Deque: " + deque);
// 删除元素
deque.removeFirst(); // 删除0
deque.pollLast(); // 删除3
System.out.println("After removing: " + deque);
// 查看元素
System.out.println("First: " + deque.getFirst()); // 1
System.out.println("Last: " + deque.getLast()); // 2
}
}
输出:
Deque: [0, 1, 2, 3]
After removing: [1, 2]
First: 1
Last: 2
4.2 当作栈使用
Deque<String> stack = new ArrayDeque<>();
stack.push("A");
stack.push("B");
stack.push("C");
while (!stack.isEmpty()) {
System.out.println(stack.pop());
}
输出:
C
B
A
4.3 当作队列使用
Deque<String> queue = new ArrayDeque<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
while (!queue.isEmpty()) {
System.out.println(queue.poll());
}
输出:
A
B
C
5. 总结
Deque是双端队列接口,支持从两端插入/删除。- 可以用作 队列(FIFO) 或 栈(LIFO)。
- 两大常用实现类:
ArrayDeque:推荐,速度快,不允许nullLinkedList:灵活,允许null
- 方法体系:
- 插入:
addFirst/addLast或offerFirst/offerLast - 删除:
removeFirst/removeLast或pollFirst/pollLast - 查看:
getFirst/getLast或peekFirst/peekLast - 栈操作:
push/pop/peek
- 插入:
Queue (单向队列) 常用方法总结
在 Java 中,Queue 是一个遵循 FIFO (先进先出) 原则的接口。为了处理可能出现的边界情况(比如试图从空队列拿东西,或者往满队列塞东西),Java 为单向队列提供了两套核心方法。
它们的核心区别在于:当操作失败时,一套会直接抛出异常导致程序崩溃,而另一套会温柔地返回一个特殊值(false 或 null)。
初始化:
Queue<Integer> queue = new LinkedList<>();
1. 核心方法对比速查表
在日常开发和算法题(如 BFS)中,强烈建议优先使用右侧“返回特殊值”的方法,以避免不必要的异常处理。
| 操作类型 | 抛出异常 (操作失败时) | 返回特殊值 (操作失败时) | 操作说明 |
|---|---|---|---|
| 入队 (队尾添加) | add(e) |
offer(e) |
将指定的元素插入队列尾部。 |
| 出队 (队头移除) | remove() |
poll() |
移除并返回队列头部的第一个元素。 |
| 查看 (仅看队头) | element() |
peek() |
获取队列头部的第一个元素,但不移除它。 |
2. 核心方法详解
🔵 推荐组:安全方法 (不抛异常)
这组方法是你在写算法题(特别是广度优先搜索 BFS)时的绝对主力。
offer(E e)- 作用: 试图将元素
e加入队尾。 - 返回值: 成功返回
true;如果队列有容量限制且已满,则返回false。
- 作用: 试图将元素
poll()- 作用: 弹出并返回队头的元素。
- 返回值: 成功返回该元素;如果队列是空的,则安全地返回
null。
peek()- 作用: 偷看一眼队头的元素,但不会把它从队列中拿走。
- 返回值: 返回队头元素;如果队列为空,返回
null。
🔴 严厉组:抛出异常的方法
这组方法通常用于你“百分之百确信队列不会为空/不会满”,或者你希望通过抛出异常来中断程序的场景。
add(E e)- 如果超出队列容量限制,会直接抛出
IllegalStateException异常。
- 如果超出队列容量限制,会直接抛出
remove()- 如果队列已经是空的,再去执行
remove(),会抛出NoSuchElementException异常。
- 如果队列已经是空的,再去执行
element()- 如果队列为空,强行查看队头,同样会抛出
NoSuchElementException异常。
- 如果队列为空,强行查看队头,同样会抛出
3. 继承自 Collection 接口的常用辅助方法
既然 Queue 继承了 Collection 接口,它自然也就拥有了一些非常实用的集合通用方法:
isEmpty()- 返回值:
boolean。如果队列中没有任何元素,返回true。 - 高频场景: 作为 BFS 的核心循环条件,例如
while (!queue.isEmpty()) { ... }。
- 返回值:
size()- 返回值:
int。返回当前队列中包含的元素个数。 - 高频场景: 在多源 BFS 或按层遍历时,用来锁定当前层级的元素数量,例如
int size = queue.size();。
- 返回值:
clear()- 作用: 一键清空队列中的所有元素。
contains(Object o)- 返回值:
boolean。判断队列内部是否包含某个特定的对象。时间复杂度通常是 O(N),因为需要遍历查找。
- 返回值:
Java BigInteger 使用笔记
1. 导入包
import java.math.BigInteger;
2. 创建 BigInteger 对象
2.1 常用构造方法
// 从字符串创建
BigInteger num1 = new BigInteger("12345678901234567890");
// 从 long 转换
BigInteger num2 = BigInteger.valueOf(100L);
// 常用常量
BigInteger.ZERO // 0
BigInteger.ONE // 1
BigInteger.TEN // 10
3. 基本运算
3.1 算术运算
BigInteger a = new BigInteger("100");
BigInteger b = new BigInteger("3");
a.add(b) // 加法:103
a.subtract(b) // 减法:97
a.multiply(b) // 乘法:300
a.divide(b) // 除法:33
a.remainder(b) // 取余:1
// 同时求商和余数
BigInteger[] result = a.divideAndRemainder(b);
result[0] // 商:33
result[1] // 余数:1
// 幂运算
BigInteger.valueOf(2).pow(10) // 1024
// 绝对值
a.abs()
// 相反数
a.negate()
3.2 比较运算
BigInteger a = new BigInteger("100");
BigInteger b = new BigInteger("200");
a.compareTo(b) // a < b 返回 -1
// a == b 返回 0
// a > b 返回 1
a.equals(b) // 是否相等
a.equals(BigInteger.ZERO) // 是否为零
3.3 位运算
BigInteger a = new BigInteger("12"); // 1100
a.and(BigInteger.valueOf(10)) // 与:1000 (8)
a.or(BigInteger.valueOf(3)) // 或:1111 (15)
a.xor(BigInteger.valueOf(5)) // 异或:1001 (9)
a.not() // 非:-13
a.shiftLeft(2) // 左移:48 (110000)
a.shiftRight(1) // 右移:6 (110)
3.4 模运算(常用)
BigInteger base = new BigInteger("123");
BigInteger mod = BigInteger.valueOf(26);
// 模加
base.add(BigInteger.valueOf(5)).mod(mod)
// 模幂:计算 (base^exponent) mod modulus
base.modPow(exponent, modulus)
// 模逆
base.modInverse(modulus)
// 取模(结果非负)
base.mod(mod)
// 取余(结果可正可负)
base.remainder(mod)
4. 类型转换
4.1 转换为基本类型
BigInteger big = new BigInteger("12345");
big.intValue() // 转换为 int(可能溢出)
big.intValueExact() // 转换为 int(溢出抛异常,推荐)
big.longValue() // 转换为 long
big.longValueExact() // 转换为 long(溢出抛异常)
big.doubleValue() // 转换为 double
big.floatValue() // 转换为 float
4.2 转换为字符串
BigInteger big = new BigInteger("255");
big.toString() // "255"
big.toString(2) // 二进制:"11111111"
big.toString(16) // 十六进制:"ff"
5. int[] 与 BigInteger[] 转换
5.1 int[] → BigInteger[]
int[] shifts = {1, 2, 3};
BigInteger[] bigints = new BigInteger[shifts.length];
// 方法1:普通循环
for (int i = 0; i < shifts.length; i++) {
bigints[i] = BigInteger.valueOf(shifts[i]);
}
// 方法2:Stream(Java 8+)
BigInteger[] bigints = Arrays.stream(shifts)
.mapToObj(BigInteger::valueOf)
.toArray(BigInteger[]::new);
5.2 BigInteger[] → int[]
BigInteger[] bigints = {...};
int[] ints = new int[bigints.length];
for (int i = 0; i < bigints.length; i++) {
ints[i] = bigints[i].intValueExact(); // 安全转换
}
6. 常见应用场景
6.1 计算阶乘
public static BigInteger factorial(int n) {
BigInteger result = BigInteger.ONE;
for (int i = 2; i <= n; i++) {
result = result.multiply(BigInteger.valueOf(i));
}
return result;
}
6.2 字母移位(取模26)
public char shift(char word, BigInteger length) {
// 获取实际移位量(取模26)
int shift = length.mod(BigInteger.valueOf(26)).intValue();
if (word >= 'a' && word <= 'z') {
return (char)('a' + (word - 'a' + shift) % 26);
}
return word;
}
6.3 后缀和计算
int[] shifts = {3, 5, 2, 7};
BigInteger[] suffixSum = new BigInteger[shifts.length];
BigInteger total = BigInteger.ZERO;
for (int i = shifts.length - 1; i >= 0; i--) {
total = total.add(BigInteger.valueOf(shifts[i]));
suffixSum[i] = total;
}
7. 注意事项
7.1 不可变性
// ❌ 错误:BigInteger 是不可变的
BigInteger num = new BigInteger("10");
num.add(new BigInteger("5")); // 结果被丢弃
// ✅ 正确:需要重新赋值
num = num.add(new BigInteger("5"));
7.2 不能使用运算符
// ❌ 错误
// BigInteger sum = a + b;
// ✅ 正确
BigInteger sum = a.add(b);
7.3 溢出检查
BigInteger big = new BigInteger("2147483648"); // 大于 int 最大值
// ❌ 静默溢出
int wrong = big.intValue(); // -2147483648
// ✅ 抛出异常
int correct = big.intValueExact(); // ArithmeticException
7.4 性能考虑
- 小数字使用基本类型(int、long)
- 只有超出 long 范围(> 2^63-1)时才使用 BigInteger
- BigInteger 运算比基本类型慢得多
8. 错误解决
常见错误:cannot find symbol
// 错误:缺少导入
public class Solution {
BigInteger num; // ❌ 找不到符号
}
// 正确:导入 BigInteger
import java.math.BigInteger;
public class Solution {
BigInteger num; // ✅ 正常
}
9. 快速参考表
| 操作 | 代码示例 |
|---|---|
| 创建 | BigInteger.valueOf(100) |
| 加法 | a.add(b) |
| 减法 | a.subtract(b) |
| 乘法 | a.multiply(b) |
| 除法 | a.divide(b) |
| 取余 | a.remainder(b) |
| 取模 | a.mod(b) |
| 比较 | a.compareTo(b) |
| 转int | a.intValueExact() |
| 转String | a.toString() |
| 幂运算 | a.pow(n) |
| 模幂 | a.modPow(exp, mod) |
Collections 算法方法快速参考表
| 方法 | 用途 | 时间复杂度 |
|---|---|---|
sort(list) |
升序排序 | O(n log n) |
sort(list, comparator) |
自定义排序 | O(n log n) |
reverse(list) |
反转顺序 | O(n) |
shuffle(list) |
随机打乱 | O(n) |
shuffle(list, random) |
指定随机源打乱 | O(n) |
rotate(list, distance) |
旋转元素(右移为正) | O(n) |
swap(list, i, j) |
交换指定位置元素 | O(1) |
fill(list, obj) |
全部填充为指定元素 | O(n) |
copy(dest, src) |
复制源列表到目标列表 | O(n) |
replaceAll(list, oldVal, newVal) |
替换所有旧值为新值 | O(n) |
binarySearch(list, key) |
二分查找(必须已排序) | O(log n) |
binarySearch(list, key, comparator) |
自定义比较器二分查找 | O(log n) |
max(list) |
返回最大值 | O(n) |
max(list, comparator) |
自定义比较器返回最大值 | O(n) |
min(list) |
返回最小值 | O(n) |
min(list, comparator) |
自定义比较器返回最小值 | O(n) |
frequency(list, obj) |
返回元素出现次数 | O(n) |
disjoint(c1, c2) |
判断两集合是否无交集 | O(n) |
indexOfSubList(source, target) |
返回子列表首次出现位置 | O(n²) |
lastIndexOfSubList(source, target) |
返回子列表最后出现位置 | O(n²) |
emptyList() |
返回空List(不可变) | O(1) |
emptySet() |
返回空Set(不可变) | O(1) |
emptyMap() |
返回空Map(不可变) | O(1) |
singletonList(obj) |
返回单元素List(不可变) | O(1) |
singletonSet(obj) |
返回单元素Set(不可变) | O(1) |
singletonMap(k, v) |
返回单元素Map(不可变) | O(1) |
unmodifiableList(list) |
返回只读List视图 | O(1) |
unmodifiableSet(set) |
返回只读Set视图 | O(1) |
unmodifiableMap(map) |
返回只读Map视图 | O(1) |
synchronizedList(list) |
返回线程安全List | O(1) |
synchronizedSet(set) |
返回线程安全Set | O(1) |
synchronizedMap(map) |
返回线程安全Map | O(1) |
checkedList(list, type) |
返回类型安全List | O(1) |
checkedSet(set, type) |
返回类型安全Set | O(1) |
checkedMap(map, keyType, valType) |
返回类型安全Map | O(1) |
addAll(collection, elements...) |
批量添加元素 | O(n) |
enumeration(collection) |
返回旧式Enumeration | O(1) |
list(enumeration) |
将Enumeration转换为List | O(n) |
常用组合示例
| 需求 | 代码组合 |
|---|---|
| 降序排序 | Collections.sort(list, Collections.reverseOrder()) |
| 获取第二大元素 | Collections.sort(list); return list.get(list.size()-2) |
| 检查列表是否已排序 | Collections.sort(sortedCopy); return list.equals(sortedCopy) |
| 循环移动k位 | Collections.rotate(list, k % list.size()) |
| 安全迭代同步集合 | synchronized (syncList) { for (item : syncList) {...} } |
| 复制并不可变 | Collections.unmodifiableList(new ArrayList<>(source)) |
| 去重并保持顺序 | new LinkedHashSet<>(list) |
| 列表转数组 | list.toArray(new Integer[0]) |
LinkList(链表)常用方法总结
| 功能 | 方法 | 示例 | 说明 |
|---|---|---|---|
| 创建链表 | new LinkedList<>() |
LinkedList<Integer> list = new LinkedList<>(); |
创建双向链表 |
| 尾部添加 | add(E e) |
list.add(10); |
默认尾插 |
| 指定位置插入 | add(int index, E e) |
list.add(1, 20); |
在指定位置插入 |
| 头部添加 | addFirst(E e) |
list.addFirst(5); |
头插 |
| 尾部添加 | addLast(E e) |
list.addLast(30); |
尾插 |
| 删除指定下标 | remove(int index) |
list.remove(1); |
删除对应位置 |
| 删除指定元素 | remove(Object o) |
list.remove(Integer.valueOf(10)); |
删除第一个匹配元素 |
| 删除头节点 | removeFirst() |
list.removeFirst(); |
删除第一个元素 |
| 删除尾节点 | removeLast() |
list.removeLast(); |
删除最后一个元素 |
| 获取指定元素 | get(int index) |
list.get(0); |
获取指定位置元素 |
| 获取头节点 | getFirst() |
list.getFirst(); |
获取第一个元素 |
| 获取尾节点 | getLast() |
list.getLast(); |
获取最后一个元素 |
| 修改元素 | set(int index, E e) |
list.set(0, 100); |
修改指定位置元素 |
| 获取长度 | size() |
list.size(); |
返回元素个数 |
| 判空 | isEmpty() |
list.isEmpty(); |
是否为空 |
| 清空链表 | clear() |
list.clear(); |
删除所有元素 |
| 是否包含元素 | contains(Object o) |
list.contains(10); |
判断元素是否存在 |
| 查找元素位置 | indexOf(Object o) |
list.indexOf(10); |
返回首次出现下标 |
| 查找最后位置 | lastIndexOf(Object o) |
list.lastIndexOf(10); |
返回最后出现下标 |
| 遍历(for-each) | for (E e : list) |
for(int x : list){} |
推荐遍历方式 |
| 迭代器遍历 | iterator() |
Iterator<Integer> it = list.iterator(); |
适合边遍历边删除 |
| 入队 | offer(E e) |
list.offer(10); |
队列尾部添加 |
| 出队 | poll() |
list.poll(); |
弹出队头 |
| 查看队头 | peek() |
list.peek(); |
查看队头不删除 |
| 入栈 | push(E e) |
list.push(10); |
栈顶压入 |
| 出栈 | pop() |
list.pop(); |
栈顶弹出 |
| 查看栈顶 | peek() |
list.peek(); |
查看栈顶 |
| 双端队列头插 | offerFirst(E e) |
list.offerFirst(1); |
双端队列操作 |
| 双端队列尾插 | offerLast(E e) |
list.offerLast(2); |
双端队列操作 |
| 双端队列头删 | pollFirst() |
list.pollFirst(); |
删除队头 |
| 双端队列尾删 | pollLast() |
list.pollLast(); |
删除队尾 |
常见快速添加元素的方法
Deque<int[]> queue = new ArrayDeque<>();
queue.offer(new int[]{i, j})
String字符串操作
1.访问Sting中的某个字符
String.charAt(index)
2.修改Sting中的某个字符
String不可变,所有修改操作都会创建新字符串对象
public class ModifyString {
public static void main(String[] args) {
String str = "Java Programming";
// 方法1:char数组
char[] chars = str.toCharArray();
chars[5] = 'P';
System.out.println(new String(chars)); // Java Programming
// 方法2:StringBuilder
StringBuilder sb = new StringBuilder(str);
sb.setCharAt(5, 'p');
System.out.println(sb.toString()); // Java programming
}
}
正则表达式re
Java 正则表达式常用方法总结
Java 正则主要使用:
import java.util.regex.*;
核心类:
| 类 | 作用 |
|---|---|
Pattern |
编译正则表达式 |
Matcher |
匹配器 |
String |
自带很多正则方法 |
1. String 里的正则方法(最常用)
| 方法 | 示例 | 说明 |
|---|---|---|
matches(regex) |
"123".matches("\\d+") |
整体匹配 |
split(regex) |
"a,b,c".split(",") |
按正则切割 |
replaceAll(regex, str) |
"a1b2".replaceAll("\\d","*") |
替换所有 |
replaceFirst(regex, str) |
"a1b2".replaceFirst("\\d","*") |
替换第一个 |
2. Pattern 类方法
编译正则
Pattern p = Pattern.compile("\\d+");
常用方法表
| 方法 | 示例 | 说明 |
|---|---|---|
compile(regex) |
Pattern.compile("\\d+") |
编译正则 |
matcher(str) |
p.matcher("123") |
创建匹配器 |
matches(regex, str) |
Pattern.matches("\\d+","123") |
静态整体匹配 |
3. Matcher 类方法(核心)
创建 Matcher
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("abc123xyz");
常用方法总表
| 方法 | 说明 |
|---|---|
find() |
查找下一个匹配 |
matches() |
整体是否匹配 |
lookingAt() |
是否从开头匹配 |
group() |
获取匹配内容 |
group(int) |
获取分组 |
start() |
匹配开始位置 |
end() |
匹配结束位置 |
replaceAll() |
替换所有 |
replaceFirst() |
替换第一个 |
4. find() 最重要
查找所有数字
Pattern p = Pattern.compile("\\d+");
Matcher m = p.matcher("a12b345c");
while (m.find()) {
System.out.println(m.group());
}
输出:
12
345
5. matches() 和 find() 区别(高频面试)
matches()
要求:
整个字符串完全匹配
"123".matches("\\d+"); // true
"abc123".matches("\\d+"); // false
find()
只要找到一部分匹配即可
Matcher m = Pattern.compile("\\d+")
.matcher("abc123");
m.find(); // true
6. 分组 group()
捕获分组
Pattern p = Pattern.compile("(\\d+)-(\\d+)");
Matcher m = p.matcher("123-456");
if (m.find()) {
System.out.println(m.group(0));
System.out.println(m.group(1));
System.out.println(m.group(2));
}
输出:
123-456
123
456
分组说明
| 写法 | 含义 |
|---|---|
group(0) |
整个匹配 |
group(1) |
第1组 |
group(2) |
第2组 |
7. split()
多字符切割
String[] arr = "a,b;c".split("[,;]");
结果:
[a, b, c]
8. replaceAll()
替换数字
String s = "a1b2c3";
System.out.println(
s.replaceAll("\\d", "*")
);
输出:
a*b*c*
9. 常用正则速查
| 正则 | 含义 |
|---|---|
. |
任意字符 |
\\d |
数字 |
\\D |
非数字 |
\\w |
字母数字下划线 |
\\W |
非单词字符 |
\\s |
空白字符 |
\\S |
非空白 |
[abc] |
abc之一 |
[^abc] |
非abc |
[a-z] |
小写字母 |
+ |
1次以上 |
* |
0次以上 |
? |
0或1次 |
{n} |
n次 |
{m,n} |
m~n次 |
^ |
开头 |
$ |
结尾 |
() |
分组 |
| |
或 |
10. Java 正则转义(超级重要)
Java 字符串里:
"\d"
是错误的。
因为:
\ 会先被 Java 字符串转义。
所以正则里的:
\d
必须写:
"\\d"
11. 实战例子
提取所有数字
String s = "abc123def45";
Matcher m = Pattern.compile("\\d+")
.matcher(s);
while (m.find()) {
System.out.println(m.group());
}
校验邮箱
String email = "test@qq.com";
boolean ok = email.matches(
"\\w+@\\w+\\.\\w+"
);
去除所有空格
s.replaceAll("\\s+", "");
12. 刷题里最常用的
| 场景 | 方法 |
|---|---|
| 判断格式 | matches() |
| 提取内容 | find() |
| 替换字符 | replaceAll() |
| 切割字符串 | split() |
| 捕获分组 | group() |
13. 一个现实建议
很多新手:
一上来就用正则解决所有字符串题。
这是错误路线。
因为正则:
- 可读性差
- 调试困难
- 性能不一定好
刷题里更合理的是:
- 简单字符串题 → 双指针
- 固定格式 → split
- 真正复杂匹配 → regex
算法
图论
环的检测
public boolean hasCycle(int[][] graph) {
int n = graph.length;
int[] state = new int[n];
for (int i = 0; i < n; i++) {
if (dfs(graph, i, state)) {
return true; // 有环
}
}
return false;
}
private boolean dfs(int[][] graph, int node, int[] state) {
if (state[node] == 1) return true; // 发现环
if (state[node] == 2) return false; // 已确认安全
state[node] = 1; // 标记为“访问中”
for (int next : graph[node]) {
if (dfs(graph, next, state)) {
return true;
}
}
state[node] = 2; // 标记为安全
return false;
}
判断逻辑(重点!)
如果你访问到一个 state == 1 的节点 → 有环
👉 因为说明你“绕回来了”(回到了当前路径上的点)
上下左右移动
public static final int[][] DIRS = {
{0,-1},{0,1},{-1,0},{1,0}
};
// ......
// BFS
while (!queue.isEmpty()) {
int[] cur = queue.poll();
for (int[] d : DIRS) {
int nr = cur[0] + d[0];
int nc = cur[1] + d[1];
if (nr >= 0 && nr < m &&
nc >= 0 && nc < n &&
dist[nr][nc] == -1) {
dist[nr][nc] =
dist[cur[0]][cur[1]] + 1;
queue.offer(new int[]{nr,nc});
}
}
}
dfs深度搜索
public static final int[][] DIRS = {
{0,-1},{0,1},{-1,0},{1,0}
};
public void dfs(int[][] grid, int row, int col) {
int m = grid.length;
int n = grid[0].length;
// 越界
if (row < 0 || row >= m || col < 0 || col >= n) {
return;
}
// 水域 或 已访问
if (grid[row][col] == 1) {
return;
}
// 淹掉
grid[row][col] = 1;
for (int[] dir : DIRS) {
dfs(grid,
row + dir[0],
col + dir[1]);
}
}
}
排序规则总结
1. Arrays.sort()
一维数组升序
Arrays.sort(nums);
自定义排序
Arrays.sort(arr, (a, b) -> 规则);
规则:
| 写法 | 含义 |
|---|---|
a - b |
升序 |
b - a |
降序 |
2. 二维数组排序
按第一列升序
Arrays.sort(arr, (a,b) -> a[0] - b[0]);
按第二列升序
Arrays.sort(arr, (a,b) -> a[1] - b[1]);
3. 多条件排序
Arrays.sort(arr, (a,b) -> { if (a[0] != b[0]) { return a[0] - b[0]; } return a[1] - b[1];});
4. Collections.sort()
用于 List
升序
Collections.sort(list);
自定义排序
Collections.sort(list, (a,b) -> b - a);
5. list.sort()
和 Collections.sort() 类似,更推荐。
升序
list.sort((a,b) -> a - b);
降序
list.sort((a,b) -> b - a);
6. 返回值规则(核心)
| 返回值 | 含义 |
|---|---|
< 0 |
a 在前 |
> 0 |
b 在前 |
= 0 |
顺序随意 |
7. 推荐写法(防溢出)
Integer.compare(a, b)
例如:
Arrays.sort(arr, (a,b) -> Integer.compare(a[0], b[0]));

浙公网安备 33010602011771号