3.25 图论
This commit is contained in:
parent
96a39cd0db
commit
5dfb8008da
83
src/main/java/graph/CanFinish.java
Normal file
83
src/main/java/graph/CanFinish.java
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目: 207. 课程表 (canFinish)
|
||||||
|
* 描述:你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
|
||||||
|
* 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
|
||||||
|
* 例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。
|
||||||
|
* 请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/course-schedule/
|
||||||
|
|
||||||
|
*/
|
||||||
|
//思路知道,代码不好写
|
||||||
|
/*
|
||||||
|
思路解析
|
||||||
|
构建图与入度数组
|
||||||
|
用一个邻接表 graph 表示课程之间的依赖关系。
|
||||||
|
对于先修课程对 [ai, bi],表示修课程 ai 之前必须完成课程 bi,因此建立一条从 bi 到 ai 的边,并将 ai 的入度加 1。
|
||||||
|
|
||||||
|
初始化队列
|
||||||
|
将所有入度为 0 的课程(即没有先修要求的课程)加入队列,作为拓扑排序的起点。
|
||||||
|
|
||||||
|
拓扑排序
|
||||||
|
从队列中取出一个课程,将其计入已完成课程数量 count。
|
||||||
|
遍历该课程的所有后续课程,将这些课程的入度减 1。如果某个课程的入度减为 0,则说明它所有的先修课程都已完成,将其加入队列。
|
||||||
|
|
||||||
|
判断是否存在环
|
||||||
|
如果最终能处理的课程数量等于总课程数,则说明不存在环路,所有课程都可以按要求完成;否则说明存在循环依赖,无法完成所有课程。
|
||||||
|
*/
|
||||||
|
public class CanFinish {
|
||||||
|
public boolean canFinish(int numCourses, int[][] prerequisites) {
|
||||||
|
// 构建图:graph.get(i) 存储课程 i 的后续课程
|
||||||
|
List<List<Integer>> graph = new ArrayList<>();
|
||||||
|
// 记录每个课程的入度(即需要先修课程的数量)
|
||||||
|
int[] indegree = new int[numCourses];
|
||||||
|
|
||||||
|
// 初始化图
|
||||||
|
for (int i = 0; i < numCourses; i++) {
|
||||||
|
graph.add(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
// 构建图和入度数组
|
||||||
|
// 每个先修对 [ai, bi] 表示:要修 ai 必须先修 bi
|
||||||
|
// 因此 bi 指向 ai,所以 indegree[ai]++
|
||||||
|
for (int[] pre : prerequisites) {
|
||||||
|
int course = pre[0];
|
||||||
|
int preCourse = pre[1];
|
||||||
|
graph.get(preCourse).add(course);
|
||||||
|
indegree[course]++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用队列存储所有入度为 0 的课程
|
||||||
|
Queue<Integer> queue = new LinkedList<>();
|
||||||
|
for (int i = 0; i < numCourses; i++) {
|
||||||
|
if (indegree[i] == 0) {
|
||||||
|
queue.offer(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 用于统计可以修的课程数
|
||||||
|
int count = 0;
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
int cur = queue.poll();
|
||||||
|
count++;
|
||||||
|
// 遍历当前课程的所有后续课程,将它们的入度减 1
|
||||||
|
for (int next : graph.get(cur)) {
|
||||||
|
indegree[next]--;
|
||||||
|
// 如果入度减到 0,说明该课程的先修都已完成
|
||||||
|
if (indegree[next] == 0) {
|
||||||
|
queue.offer(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果所有课程都能修完,则 count 应等于 numCourses
|
||||||
|
return count == numCourses;
|
||||||
|
}
|
||||||
|
}
|
37
src/main/java/graph/MaxAreaOfIsland.java
Normal file
37
src/main/java/graph/MaxAreaOfIsland.java
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
package graph;
|
||||||
|
/**
|
||||||
|
* 题目: 695. 岛屿的最大面积 (maxAreaOfIsland)
|
||||||
|
* 描述:给你一个大小为 m x n 的二进制矩阵 grid 。
|
||||||
|
* 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
|
||||||
|
* 岛屿的面积是岛上值为 1 的单元格的数目。
|
||||||
|
* 计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/max-area-of-island/
|
||||||
|
|
||||||
|
*/
|
||||||
|
public class MaxAreaOfIsland {
|
||||||
|
int dfs(int[][] grid,int i,int j){
|
||||||
|
if(i<0 || i>=grid.length || j<0 || j>=grid[0].length || grid[i][j]==0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
int area = 1;
|
||||||
|
grid[i][j]=0;
|
||||||
|
area+=dfs(grid,i,j-1);
|
||||||
|
area+=dfs(grid,i,j+1);
|
||||||
|
area+=dfs(grid,i+1,j);
|
||||||
|
area+=dfs(grid,i-1,j);
|
||||||
|
return area;
|
||||||
|
}
|
||||||
|
public int maxAreaOfIsland(int[][] grid) {
|
||||||
|
int maxx=0;
|
||||||
|
for (int i = 0; i < grid.length; i++) {
|
||||||
|
for (int j = 0; j < grid[0].length; j++) {
|
||||||
|
if(grid[i][j]==1) {
|
||||||
|
int area = dfs(grid, i, j);
|
||||||
|
maxx = Math.max(maxx, area);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return maxx;
|
||||||
|
}
|
||||||
|
}
|
50
src/main/java/graph/NumIslands.java
Normal file
50
src/main/java/graph/NumIslands.java
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目: 200. 岛屿数量 (numIslands)
|
||||||
|
* 描述:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
|
||||||
|
* 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
|
||||||
|
* 此外,你可以假设该网格的四条边均被水包围。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/number-of-islands/
|
||||||
|
|
||||||
|
*/
|
||||||
|
|
||||||
|
public class NumIslands {
|
||||||
|
void dfs(char[][] grid, int r, int c) {
|
||||||
|
int nr = grid.length;
|
||||||
|
int nc = grid[0].length;
|
||||||
|
|
||||||
|
if (r < 0 || c < 0 || r >= nr || c >= nc || grid[r][c] == '0') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
grid[r][c] = '0';
|
||||||
|
dfs(grid, r - 1, c);
|
||||||
|
dfs(grid, r + 1, c);
|
||||||
|
dfs(grid, r, c - 1);
|
||||||
|
dfs(grid, r, c + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int numIslands(char[][] grid) {
|
||||||
|
if (grid == null || grid.length == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int nr = grid.length;
|
||||||
|
int nc = grid[0].length;
|
||||||
|
int num_islands = 0;
|
||||||
|
for (int r = 0; r < nr; ++r) {
|
||||||
|
for (int c = 0; c < nc; ++c) {
|
||||||
|
if (grid[r][c] == '1') {
|
||||||
|
++num_islands;
|
||||||
|
dfs(grid, r, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return num_islands;
|
||||||
|
}
|
||||||
|
}
|
73
src/main/java/graph/OrangesRotting.java
Normal file
73
src/main/java/graph/OrangesRotting.java
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
package graph;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.Queue;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目: 994. 腐烂的橘子 (orangesRotting)
|
||||||
|
* 描述:在给定的 m x n 网格 grid 中,每个单元格可以有以下三个值之一:
|
||||||
|
* 值 0 代表空单元格;
|
||||||
|
* 值 1 代表新鲜橘子;
|
||||||
|
* 值 2 代表腐烂的橘子。
|
||||||
|
* 每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
|
||||||
|
* 返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/rotting-oranges/
|
||||||
|
|
||||||
|
*/
|
||||||
|
//思想会,代码写不利索
|
||||||
|
public class OrangesRotting {
|
||||||
|
public int orangesRotting(int[][] grid) {
|
||||||
|
int rows = grid.length;
|
||||||
|
int cols = grid[0].length;
|
||||||
|
Queue<int[]> queue = new LinkedList<>();
|
||||||
|
int fresh = 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) {
|
||||||
|
fresh++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果一开始没有新鲜橘子,则不需要任何时间
|
||||||
|
if (fresh == 0) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int minutes = 0;
|
||||||
|
// 四个方向:上、下、左、右
|
||||||
|
int[][] directions = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
|
||||||
|
|
||||||
|
// BFS 开始,每一层代表一分钟
|
||||||
|
while (!queue.isEmpty()) {
|
||||||
|
int size = queue.size();
|
||||||
|
boolean rotted = false; // 当前这一分钟是否有新鲜橘子被腐烂
|
||||||
|
for (int i = 0; i < size; i++) {
|
||||||
|
int[] cur = queue.poll();
|
||||||
|
for (int[] dir : directions) {
|
||||||
|
int x = cur[0] + dir[0];
|
||||||
|
int y = cur[1] + dir[1];
|
||||||
|
// 检查边界条件和是否为新鲜橘子
|
||||||
|
if (x >= 0 && x < rows && y >= 0 && y < cols && grid[x][y] == 1) {
|
||||||
|
// 腐烂新鲜橘子,并入队后续继续扩散
|
||||||
|
grid[x][y] = 2;
|
||||||
|
fresh--;
|
||||||
|
queue.offer(new int[]{x, y});
|
||||||
|
rotted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果这一分钟有橘子腐烂,则分钟数加1
|
||||||
|
if (rotted) {
|
||||||
|
minutes++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 如果还有新鲜橘子,说明无法全部腐烂,返回 -1
|
||||||
|
return fresh == 0 ? minutes : -1;
|
||||||
|
}
|
||||||
|
}
|
58
src/main/java/graph/Trie.java
Normal file
58
src/main/java/graph/Trie.java
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
package graph;
|
||||||
|
/**
|
||||||
|
* 题目: 208. 实现 Trie (前缀树)
|
||||||
|
* 描述:Trie(发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
|
||||||
|
* 请你实现 Trie 类:
|
||||||
|
* Trie() 初始化前缀树对象。
|
||||||
|
* void insert(String word) 向前缀树中插入字符串 word 。
|
||||||
|
* boolean search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。
|
||||||
|
* boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/implement-trie-prefix-tree/description/
|
||||||
|
|
||||||
|
*/
|
||||||
|
//没搞懂 学习一下
|
||||||
|
class Trie {
|
||||||
|
private Trie[] children;
|
||||||
|
private boolean isEnd;
|
||||||
|
|
||||||
|
public Trie() {
|
||||||
|
children = new Trie[26]; //这行代码只是 创建了一个长度为 26 的数组,对应 a ~ z。但是!数组里的元素初始都是 null,只有当需要用到某个字符时,才会创建对应的 Trie 节点。
|
||||||
|
isEnd = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void insert(String word) {
|
||||||
|
Trie node = this;
|
||||||
|
for (int i = 0; i < word.length(); i++) {
|
||||||
|
char ch = word.charAt(i);
|
||||||
|
int index = ch - 'a';
|
||||||
|
if (node.children[index] == null) {
|
||||||
|
node.children[index] = new Trie();
|
||||||
|
}
|
||||||
|
node = node.children[index];
|
||||||
|
}
|
||||||
|
node.isEnd = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean search(String word) {
|
||||||
|
Trie node = searchPrefix(word);
|
||||||
|
return node != null && node.isEnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean startsWith(String prefix) {
|
||||||
|
return searchPrefix(prefix) != null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Trie searchPrefix(String prefix) {
|
||||||
|
Trie node = this;
|
||||||
|
for (int i = 0; i < prefix.length(); i++) {
|
||||||
|
char ch = prefix.charAt(i);
|
||||||
|
int index = ch - 'a';
|
||||||
|
if (node.children[index] == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
node = node.children[index];
|
||||||
|
}
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
}
|
47
src/main/java/tree/LowestCommonAncestor.java
Normal file
47
src/main/java/tree/LowestCommonAncestor.java
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
package tree;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.HashSet;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目: 236. 二叉树的最近公共祖先 (lowestCommonAncestor)
|
||||||
|
* 描述:给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
|
||||||
|
* 百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
|
||||||
|
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/lowest-common-ancestor-of-a-binary-tree/
|
||||||
|
|
||||||
|
*/
|
||||||
|
public class LowestCommonAncestor {
|
||||||
|
void helper(HashMap<TreeNode, TreeNode> map, TreeNode father, TreeNode cur) {
|
||||||
|
if (cur == null)
|
||||||
|
return;
|
||||||
|
map.put(cur, father);
|
||||||
|
helper(map, cur, cur.left);
|
||||||
|
helper(map, cur, cur.right);
|
||||||
|
}
|
||||||
|
|
||||||
|
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
|
||||||
|
HashMap<TreeNode, TreeNode> map = new HashMap<>();
|
||||||
|
HashSet<TreeNode> set = new HashSet<>();
|
||||||
|
map.put(root, null);
|
||||||
|
helper(map, root, root.left);
|
||||||
|
helper(map, root, root.right);
|
||||||
|
|
||||||
|
// 将 p 以及其所有祖先加入 set
|
||||||
|
TreeNode tp = p;
|
||||||
|
while (tp != null) {
|
||||||
|
set.add(tp);
|
||||||
|
tp = map.get(tp);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从 q 开始,找到第一个出现在 p 祖先链中的节点
|
||||||
|
tp = q;
|
||||||
|
while (tp != null) {
|
||||||
|
if (set.contains(tp))
|
||||||
|
return tp;
|
||||||
|
tp = map.get(tp);
|
||||||
|
}
|
||||||
|
return null; // 理论上不可能执行到这里,因为肯定存在公共祖先
|
||||||
|
}
|
||||||
|
}
|
63
src/main/java/tree/PathSum.java
Normal file
63
src/main/java/tree/PathSum.java
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
package tree;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 题目: 437. 路径总和 III (pathSum)
|
||||||
|
* 描述:给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于 targetSum 的 路径 的数目。
|
||||||
|
* 路径 不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
|
||||||
|
|
||||||
|
* 链接:https://leetcode.cn/problems/path-sum-iii/
|
||||||
|
|
||||||
|
*/
|
||||||
|
//前缀和想到了,代码不会写
|
||||||
|
public class PathSum {
|
||||||
|
// 主函数:用于计算路径和等于 targetSum 的路径个数
|
||||||
|
public int pathSum(TreeNode root, int targetSum) {
|
||||||
|
// 使用一个哈希表来记录前缀和出现的次数
|
||||||
|
// key:从根节点到当前节点的累计和(前缀和)
|
||||||
|
// value:该前缀和出现的次数
|
||||||
|
Map<Long, Integer> prefix = new HashMap<Long, Integer>();
|
||||||
|
// 初始化:前缀和为 0 的情况出现 1 次(方便处理从根节点开始的情况)
|
||||||
|
prefix.put(0L, 1);
|
||||||
|
// 从根节点开始递归查找满足条件的路径
|
||||||
|
return dfs(root, prefix, 0, targetSum);
|
||||||
|
}
|
||||||
|
|
||||||
|
// dfs函数:递归遍历树,并统计满足路径和为 targetSum 的路径个数
|
||||||
|
// 参数说明:
|
||||||
|
// root:当前节点
|
||||||
|
// prefix:记录前缀和及其出现次数的哈希表
|
||||||
|
// curr:当前节点的累计前缀和(从根节点到当前节点的和)
|
||||||
|
// targetSum:目标路径和
|
||||||
|
public int dfs(TreeNode root, Map<Long, Integer> prefix, long curr, int targetSum) {
|
||||||
|
// 终止条件:当前节点为空,返回0条路径
|
||||||
|
if (root == null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
int ret = 0;
|
||||||
|
// 更新当前累计前缀和:加上当前节点的值
|
||||||
|
curr += root.val;
|
||||||
|
|
||||||
|
// 检查是否存在某个前缀和,使得:
|
||||||
|
// 当前累计和 - 该前缀和 = targetSum
|
||||||
|
// 即:从那个节点到当前节点的路径和等于 targetSum
|
||||||
|
ret = prefix.getOrDefault(curr - targetSum, 0);
|
||||||
|
|
||||||
|
// 更新哈希表:将当前前缀和的出现次数加1
|
||||||
|
prefix.put(curr, prefix.getOrDefault(curr, 0) + 1);
|
||||||
|
|
||||||
|
// 递归遍历左子树和右子树
|
||||||
|
ret += dfs(root.left, prefix, curr, targetSum);
|
||||||
|
ret += dfs(root.right, prefix, curr, targetSum);
|
||||||
|
|
||||||
|
// 回溯操作:在返回父节点之前,去掉当前节点对前缀和的贡献
|
||||||
|
// 防止当前路径的前缀和影响到其他分支的计算
|
||||||
|
prefix.put(curr, prefix.getOrDefault(curr, 0) - 1);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
x
Reference in New Issue
Block a user