diff --git a/src/main/java/graph/CanFinish.java b/src/main/java/graph/CanFinish.java new file mode 100644 index 0000000..c45b2ef --- /dev/null +++ b/src/main/java/graph/CanFinish.java @@ -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> 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 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; + } +} diff --git a/src/main/java/graph/MaxAreaOfIsland.java b/src/main/java/graph/MaxAreaOfIsland.java new file mode 100644 index 0000000..353ea04 --- /dev/null +++ b/src/main/java/graph/MaxAreaOfIsland.java @@ -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; + } +} diff --git a/src/main/java/graph/NumIslands.java b/src/main/java/graph/NumIslands.java new file mode 100644 index 0000000..3c303a4 --- /dev/null +++ b/src/main/java/graph/NumIslands.java @@ -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; + } +} diff --git a/src/main/java/graph/OrangesRotting.java b/src/main/java/graph/OrangesRotting.java new file mode 100644 index 0000000..23673f3 --- /dev/null +++ b/src/main/java/graph/OrangesRotting.java @@ -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 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; + } +} diff --git a/src/main/java/graph/Trie.java b/src/main/java/graph/Trie.java new file mode 100644 index 0000000..a43b1cf --- /dev/null +++ b/src/main/java/graph/Trie.java @@ -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; + } +} diff --git a/src/main/java/tree/LowestCommonAncestor.java b/src/main/java/tree/LowestCommonAncestor.java new file mode 100644 index 0000000..12fda7e --- /dev/null +++ b/src/main/java/tree/LowestCommonAncestor.java @@ -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 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 map = new HashMap<>(); + HashSet 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; // 理论上不可能执行到这里,因为肯定存在公共祖先 + } +} diff --git a/src/main/java/tree/PathSum.java b/src/main/java/tree/PathSum.java new file mode 100644 index 0000000..1d19202 --- /dev/null +++ b/src/main/java/tree/PathSum.java @@ -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 prefix = new HashMap(); + // 初始化:前缀和为 0 的情况出现 1 次(方便处理从根节点开始的情况) + prefix.put(0L, 1); + // 从根节点开始递归查找满足条件的路径 + return dfs(root, prefix, 0, targetSum); + } + + // dfs函数:递归遍历树,并统计满足路径和为 targetSum 的路径个数 + // 参数说明: + // root:当前节点 + // prefix:记录前缀和及其出现次数的哈希表 + // curr:当前节点的累计前缀和(从根节点到当前节点的和) + // targetSum:目标路径和 + public int dfs(TreeNode root, Map 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; + } +} +