图论
Solution200. 岛屿数量
如图,有三处岛屿
/**
* 一开始,从[0][0]处开始遍历,一开始遍历到了左上角的那座岛屿
* 从[0][0]开始,把[0][0]由1处理为0,然后遍历[0][0]的上下左右
* 遍历[0][0]的上:[-1][0],越界了,退出
* 遍历[0][0]的下:[1][0]的位置是1,把[1][0]由1处理为0,然后遍历[1][0]的上下左右
* 遍历[1][0]的上:[0][0]位置已经是0了,退出
* 遍历[1][0]的下:[2][0]位置已经是0了,退出
* 遍历[1][0]的左:[1][-1]位置越界了,退出
* 遍历[1][0]的右:[1][1]的位置是1,把[1][1]由1处理为0,然后遍历[1][1]的上下左右
* 遍历[1][1]的上:[0][1]的位置是1,把[0][1]由1处理为0,然后遍历[0][1]的上下左右([0][1]的上下左右均是0,退出)
* 遍历[1][1]的下:[2][1]位置已经是0了,退出
* 遍历[1][1]的左:[1][0]位置已经是0了,退出
* 遍历[1][1]的右:[1][2]位置已经是0了,退出
* 遍历[0][0]的左:[0][-1],越界了,退出
* 遍历[0][0]的右:[0][1]位置已经是0了,退出
* 第一座岛屿的已经全是0了,第一座岛屿遍历结束
*
* 图解第一座岛屿的变化过程:
* [0][0]由1处理为0 把[1][0]由1处理为0 [1][1]由1处理为0 [0][1]由1处理为0 第一座岛屿的已经全是0了,第一座岛屿遍历结束
* {'1', '1', '0', '0', '0'}, {'0', '1', '0', '0', '0'}, {'0', '1', '0', '0', '0'}, {'0', '1', '0', '0', '0'}, {'0', '0', '0', '0', '0'},
* {'1', '1', '0', '0', '0'}, {'1', '1', '0', '0', '0'}, {'0', '1', '0', '0', '0'}, {'0', '0', '0', '0', '0'}, {'0', '0', '0', '0', '0'},
* {'0', '0', '1', '0', '0'}, ———> {'0', '0', '1', '0', '0'}, ———> {'0', '0', '1', '0', '0'}, ———> {'0', '0', '1', '0', '0'}, ———> {'0', '0', '1', '0', '0'},
* {'0', '0', '0', '1', '1'} {'0', '0', '0', '1', '1'} {'0', '0', '0', '1', '1'} {'0', '0', '0', '1', '1'} {'0', '0', '0', '1', '1'}
*/
public class Solution {
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) return 0;
int count = 0;
int rows = grid.length;
int cols = grid[0].length;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
// 发现一个新的岛屿,开始DFS
dfs(grid, i, j);
count++; // 每完成一次DFS,岛屿数量增加1
}
}
}
return count;
}
// 深度优先搜索函数,将当前陆地及其相邻的陆地全部标记为已访问('0')
private void dfs(char[][] grid, int row, int col) {
int rows = grid.length;
int cols = grid[0].length;
// 边界条件判断,或者当前位置已经访问过('0')
if (row < 0 || row >= rows || col < 0 || col >= cols || grid[row][col] == '0') {
return;
}
// 将当前位置标记为已访问
grid[row][col] = '0';
// 对当前陆地的上、下、左、右四个方向进行DFS
dfs(grid, row - 1, col); // 上
dfs(grid, row + 1, col); // 下
dfs(grid, row, col - 1); // 左
dfs(grid, row, col + 1); // 右
}
public static void main(String[] args) {
Solution solution = new Solution();
// 测试用例
char[][] grid = {
{
'1', '1', '0', '0', '0'},
{
'1', '1', '0', '0', '0'},
{
'0', '0', '1', '0', '0'},
{
'0', '0', '0', '1', '1'}
};
int result = solution.numIslands(grid);
System.out.println("岛屿数量: " + result); // 输出: 岛屿数量: 1
}
}
Solution994. 腐烂的橘子
/**
* {2, 1, 1},
* {1, 1, 0},
* {0, 1, 1}
*
* 一开始,统计新鲜的橘子的数量=6,把腐烂的橘子的坐标[0][0]放到queue里面,queue的大小为1
* 遍历queue里面的这一条数据
* 从queue里面取出腐烂的橘子的坐标[0][0](取出来后queue为空了),遍历[0][0]的上下左右
* 遍历[0][0]的上:[-1][0],越界了,退出
* 遍历[0][0]的下:[1][0]的位置是1,把[1][0]由1处理为2,新鲜的橘子的数量=5,把腐烂的橘子的坐标[1][0]放到queue里面,queue里面的内容是[[1][0]]
* 遍历[0][0]的左:[0][-1],越界了,退出
* 遍历[0][0]的右:[0][1]的位置是1,把[0][1]由1处理为2,新鲜的橘子的数量=4,把腐烂的橘子的坐标[0][1]放到queue里面,queue里面的内容是[[1][0],[0][1]]
* [0][0]的上下左右遍历完毕,此时新鲜的橘子的数量=4,queue里面的内容是[[1][0],[0][1]],用时minutes=1,queue的大小为2
* 遍历queue里面的这两条数据:
* 从queue里面取出腐烂的橘子的坐标[1][0](取出来后queue内容是[[0][1]]),遍历[1][0]的上下左右
* 遍历[1][0]的上:[0][0]的位置是2,退出
* 遍历[1][0]的下:[2][0]的位置是0,退出
* 遍历[1][0]的左:[1][-1],越界了,退出
* 遍历[1][0]的右:[1][1]的位置是1,把[1][1]由1处理为2,新鲜的橘子的数量=3,把腐烂的橘子的坐标[1][1]放到queue里面,queue里面的内容是[[0][1],[1][1]]
* [1][0]的上下左右遍历完毕
* 从queue里面取出腐烂的橘子的坐标[0][1](取出来后queue内容是[[1][1]]),遍历[0][1]的上下左右
* 遍历[0][1]的上:[-1][1]越界了,退出
* 遍历[0][1]的下:[1][1]的位置已经是2了,退出
* 遍历[0][1]的左:[0][0]的位置已经是2了,退出
* 遍历[0][1]的右:[0][2]的位置是1,把[0][2]由1处理为2,新鲜的橘子的数量=2,把腐烂的橘子的坐标[0][2]放到queue里面,queue里面的内容是[[1][1],[0][2]]
* [0][1]的上下左右遍历完毕
* 第二遍遍历完毕,此时新鲜的橘子的数量=2,queue里面的内容是[[1][1],[0][2]],已经用时minutes=2,queue的大小为2
*
* 图解变化过程:
* 1分钟后 2分钟后 3分钟后 4分钟后
* {2, 1, 1}, {2, 2, 1}, {2, 2, 2}, {2, 2, 2}, {2, 2, 2},
* {1, 1, 0}, ———> {2, 1, 0}, ———> {2, 2, 0}, ———> {2, 2, 0}, ———> {2, 2, 0},
* {0, 1, 1} {0, 1, 1} {0, 1, 1} {0, 2, 1} {0, 2, 2}
*/
public class Solution {
private static final int[][] DIRECTIONS = {
{
-1, 0}, {
1, 0}, {
0, -1}, {
0, 1}};
public int orangesRotting(int[][] grid) {
int rows = grid.length;
int cols = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
int freshOranges = 0;
// 初始化队列,将所有腐烂的橘子加入队列,并计算新鲜橘子的数量
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == 2) {
queue.offer(new int[]{
i, j});
} else if (grid[i][j] == 1) {
freshOranges++;
}
}
}
int minutes = 0;
// BFS遍历,直到队列为空
while (!queue.isEmpty()) {
int size = queue.size();
// 遍历当前层的所有腐烂橘子
for (int i = 0; i < size; i++) {
int[] orange = queue.poll();
int row = orange[0];
int col = orange[1];
// 检查四个方向上的相邻单元格
for (int[] direction : DIRECTIONS) {
int newRow = row + direction[0];
int newCol = col + direction[1];
// 如果相邻单元格是新鲜橘子,则将其腐烂并加入队列,同时减少新鲜橘子的数量
if (newRow >= 0 && newRow < rows && newCol >= 0 && newCol < cols && grid[newRow][newCol] == 1) {
grid[newRow][newCol] = 2;
queue.offer(new int[]{
newRow, newCol});
freshOranges--;
}
}
}
// 完成了一层遍历,分钟数加一
if (!queue.isEmpty()) {
minutes++;
}
}
// 如果还有新鲜橘子剩余,则返回-1;否则返回分钟数
return freshOranges == 0 ? minutes : -1;
}
public static void main(String[] args) {
Solution solution = new Solution();
int[][] grid = {
{
2, 1, 1},
{
1, 1, 0},
{
0, 1, 1}
};
int result = solution.orangesRotting(grid);
System.out.println("经过的最小分钟数: " + result); // 输出应为4
// 树的执行流程解释:
// 1. 初始化队列和新鲜橘子计数,找到所有腐烂橘子入队。
// 2. 开始BFS遍历,每次从队列中取出一个腐烂橘子位置。
// 3. 检查该橘子周围四个方向上的新鲜橘子,如果找到,则腐烂该橘子,并将其位置入队。
// 4. 每一轮BFS遍历代表经过了一分钟,分钟数加一。
// 5. 当队列为空或没有新鲜橘子时,遍历结束。如果还有新鲜橘子则返回-1,否则返回分钟数减一。
}
}
Solution207. 课程表
/**
* 输入的课程如下:
* {1, 0},
* {2, 0},
* {3, 1},
* {3, 2}
*
* 构建课程依赖关系图
* 0 -> 1 -> 3
* 0 -> 2 -> 3
* 表示学习课程 1 前要先学课程 0,学习课程 2 前要先学课程 0,学习课程 3 前要先学课程 1 和 2
*
* 思路:
* 初始化,adjList=[[1,2],[3],[3],[]],numCourses=4表示有四门课,visted=[0,0,0,0]表示四个元素均未访问(visted可能是0或1或2,而0表示未曾访问,1表示正在访问,2表示访问完毕)
* 一开始,对第0门课程进行学习,将visted[0]置为1此时visted=[1,0,0,0],拿到adjList的第0个元素[1,2](表示学习0之前需要学习1和2),所以遍历这俩元素:
* 访问元素1,将visted[1]置为1此时visted=[1,1,0,0],访问adjList的第1个元素[3](表示学习1之前需要学习3),然后遍历这个元素:
* 访问元素3,将visted[3]置为1此时visted=[1,1,0,1],访问adjList的第3个元素[],由于adjList的第3个元素为空,所以将visted[3]置为2此时visted=[1,1,0,2]
* adjList的第1个元素[3]访问完毕,之前说的学习1之前需要学习3,现在3学完了那么1可以学习了,学完后将visted[1]置为2此时visted=[1,2,0,2]
* 访问元素2,将visted[2]置为1此时visted=[1,2,1,2],访问adjList的第2个元素[3](表示学习2之前需要学习3),然后遍历这个元素:
* 访问元素3,由于visted[3]已经是2了,说明这个元素已经访问过了,那就退出访问
* adjList的第2个元素[3]访问完毕,之前说的学习2之前需要学习3,现在3学完了那么2可以学习了,学完后将visted[2]置为2此时visted=[1,2,2,2]
* adjList的第0个元素[1,2]访问完毕,学习0之前需要学习1和2,现在1和2都学完了,将visted[0]置为2此时visted=[2,2,2,2]
* 然后,对第1门课程进行学习,由于visted[1]已经是2了,说明这个元素已经访问过了,那就退出访问
* 然后,对第2门课程进行学习,由于visted[2]已经是2了,说明这个元素已经访问过了,那就退出访问
* 然后,对第3门课程进行学习,由于visted[3]已经是2了,说明这个元素已经访问过了,那就退出访问
* 遍历结束,不存在环
*/
public class Solution {
// 图的邻接表表示
private List<List<Integer>> adjList;
// 记录访问状态,0 表示未访问,1 表示正在访问,2 表示已访问完
private int[] visited;
public boolean canFinish(int numCourses, int[][] prerequisites) {
adjList = new ArrayList<>();
for (int i = 0; i < numCourses; i++) {
adjList.add(new ArrayList<>());
}
visited = new int[numCourses];
// 构建邻接表
for (int[] prerequisite : prerequisites) {
int course = prerequisite[0];
int prerequisiteCourse = prerequisite[1];
adjList.get(prerequisiteCourse).add(course);
}
// 对每一门课程进行 DFS 遍历
for (int i = 0; i < numCourses; i++) {
if (!dfs(i)) {
return false; // 存在环,返回 false
}
}
return true; // 不存在环,返回 true
}
// DFS 遍历函数
private boolean dfs(int course) {
if (visited[course] == 1) {
// 发现环,正在访问的节点被再次访问
return false;
}
if (visited[course] == 2) {
// 已经访问完的节点,直接返回 true
return true;
}
// 标记为正在访问
visited[course] = 1;
// 递归访问所有邻接节点
for (int nextCourse : adjList.get(course)) {
if (!dfs(nextCourse)) {
return false;
}
}
// 标记为已访问完
visited[course] = 2;
return true;
}
public static void main(String[] args) {
Solution cs = new Solution();
int numCourses = 4;
int[][] prerequisites = {
{
1, 0},
{
2, 0},
{
3, 1},
{
3, 2}
};
// 构建课程依赖关系图
// 0 -> 1, 0 -> 2, 1 -> 3, 2 -> 3
// 表示学习课程 1 前要先学课程 0,学习课程 2 前要先学课程 0,学习课程 3 前要先学课程 1 和 2
boolean canComplete = cs.canFinish(numCourses, prerequisites);
System.out.println("是否可以完成所有课程的学习: " + canComplete);
}
}
Solution208. 实现 Trie (前缀树)
示例:
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]
解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 True
trie.search("app"); // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app"); // 返回 True
1、初始化 Trie:
root
2、插入 "apple":
root
|
a
|
p
|
p
|
l
|
e (isEnd = true)
3、插入 "app":
root
|
a
|
p
|
p (isEnd = true)
|
l
|
e (isEnd = true)
class TrieNode {
// 子节点数组,存储26个小写字母
private TrieNode[] children;
// 标记当前节点是否是一个单词的结尾
private boolean isEnd;
public TrieNode() {
children = new TrieNode[26]; // 初始化子节点数组
isEnd = false; // 默认不是单词的结尾
}
// 检查当前节点是否包含某个字符的子节点
public boolean containsKey(char ch) {
return children[ch - 'a'] != null;
}
// 获取某个字符对应的子节点
public TrieNode get(char ch) {
return children[ch - 'a'];
}
// 插入一个字符的子节点
public void put(char ch, TrieNode node) {
children[ch - 'a'] = node;
}
// 设置当前节点为单词的结尾
public void setEnd() {
isEnd = true;
}
// 检查当前节点是否是单词的结尾
public boolean isEnd() {
return isEnd;
}
}
class Trie {
private TrieNode root; // 根节点
// 初始化前缀树
public Trie() {
root = new TrieNode();
}
// 插入一个单词到前缀树中
public void insert(String word) {
TrieNode node = root;
for (int i = 0; i < word.length(); i++) {
char currentChar = word.charAt(i);
if