leetcode hot100 图论

发布于:2025-03-13 ⋅ 阅读:(11) ⋅ 点赞:(0)

9️⃣ 图论

200. 岛屿数量

给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。此外,你可以假设该网格的四条边均被水包围。

题解:

  • 二维数组, 遍历遇到当前值为1的, 岛屿数加一, 然后进行岛屿治理–dfs深度遍历当前值所在的岛屿, 将该岛屿所在的其他值全部置为’2’, 那么继续遍历时就不会重复计算

  • class Solution {
        public int numIslands(char[][] grid) {
            int isLand = 0;
            for(int i = 0;i<grid.length;i++){
                for(int j=0;j<grid[0].length;j++){
                    if(grid[i][j]=='1'){
                        dfs(grid,i,j);
                        isLand++;
                    }
                }
            }
            return isLand;
        }
        public void dfs(char[][] grid,int i, int j){
            if(i<0||j<0||i>=grid.length||j>=grid[0].length||grid[i][j]!='1'){
                return ;
            }
            grid[i][j]='2';
            dfs(grid,i-1,j);
            dfs(grid,i+1,j);
            dfs(grid,i,j+1);
            dfs(grid,i,j-1);
        }
    }
    

994. 腐烂的橘子

在给定的 m x n网格grid 中,每个单元格可以有以下三个值之一:值 0 代表空单元格;值 1 代表新鲜橘子;值 2 代表腐烂的橘子。每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂.返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1 。

题解:

  • 先遍历统计新鲜数量和腐烂节点,再bfs进行腐烂过程并计时,腐烂结束看看还有没有新鲜橙子

  • class Solution {
        public int orangesRotting(int[][] grid) {
            // 统计新鲜橙子
            int freshNum = 0;
            // dfs 统计腐烂橙子
            Deque<int[]> queue = new ArrayDeque<>();
            for (int i = 0; i < grid.length; i++) {
                for (int j = 0; j < grid[i].length; j++) {
                    if (grid[i][j] == 1) {
                        freshNum++;
                    }
                    if (grid[i][j] == 2) {
                        queue.offer(new int[]{i, j});
                    }
                }
            }
            int minutes = 0;
            while (!queue.isEmpty()) {
                if (freshNum == 0) {
                    // 没有新鲜橙子了
                    return minutes;
                }
                // 过去1分钟,周围开始腐烂
                minutes++;
                int size = queue.size();
                for (int i = 0; i < size; i++) {
                    int[] rot = queue.pop();
                    int x = rot[0];
                    int y = rot[1];
                    freshNum -= roting(grid, x - 1, y, queue);
                    freshNum -= roting(grid, x + 1, y, queue);
                    freshNum -= roting(grid, x, y - 1, queue);
                    freshNum -= roting(grid, x, y + 1, queue);
                }
            }
            // 腐烂过程结束
            return freshNum > 0 ? -1 : minutes;
        }
    
        private int roting(int[][] grid, int x, int y, Deque<int[]> queue) {
            if (x < 0 || y < 0 || x > grid.length - 1 || y > grid[0].length - 1 || grid[x][y] != 1) {
                return 0;
            }
            // grid[x][y] = 1
            grid[x][y] = 2;
            queue.offer(new int[]{x, y});
            return 1;
        }
    }
    

207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。例如,先修课程对 [0, 1] 表示:想要学习课程 0 ,你需要先完成课程 1 。请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。

题解:

  • 拓扑排序(Topological Sorting)

    拓扑排序是一种针对 有向无环图(DAG) 的排序方法,它的特点是:

    • 如果有一条从 A 指向 B 的边(A → B),那么 A 必须排在 B 之前。

    • 用于解决

      依赖关系

      问题,比如:

      • 课程安排(先修课依赖)
      • 任务调度(某些任务必须先完成)
      • 编译依赖(文件编译顺序)
  • 拓扑排序题, 可以用dfs深度优先搜索,查找拓扑顺序;也可以广度优先, 优先查找入度为0的点, 再将拓扑中它指向的节点的入度减一,直至无环或者存在环

  • 广度优先bfs,理解起来比较简单:

    • class Solution {
          List<List<Integer>> edges;
          int[] indeg;
      
          public boolean canFinish(int numCourses, int[][] prerequisites) {
              edges = new ArrayList<List<Integer>>();
              for (int i = 0; i < numCourses; ++i) {
                  edges.add(new ArrayList<Integer>());
              }
              indeg = new int[numCourses];
              for (int[] info : prerequisites) {
                  edges.get(info[1]).add(info[0]);
                  ++indeg[info[0]];
              }
      
              Queue<Integer> queue = new LinkedList<Integer>();
              for (int i = 0; i < numCourses; ++i) {
                  if (indeg[i] == 0) {
                      queue.offer(i);
                  }
              }
      
              int visited = 0;
              while (!queue.isEmpty()) {
                  ++visited;
                  int u = queue.poll();
                  for (int v: edges.get(u)) {
                      --indeg[v];
                      if (indeg[v] == 0) {
                          queue.offer(v);
                      }
                  }
              }
      
              return visited == numCourses;
          }
      }
      
  • 深度优先dfs:

    • 为每个节点构建一个有向边的指向图, 这代编选修该门课后才能选秀其他课, 遍历给出的先修数组, 添加进该图(实际是一个填装正数的List)

    • 将所有课目的访问值置为0代表未访问(1代表正在访问,2代表访问完成)

    • 遇到一个visited为0的节点, 深度遍历该节点的指向图, 并且将路过的节点访问值都为1代表正在访问 ,直至访问结束, 如果未结束且访问到visited值为1的节点, 代表该路径当前已存在环路(之前已访问过该节点),将成员变量valid置为false

    • class Solution {
          List<List<Integer>> edges;
          int[] visited;
          boolean valid = true;
      
          public boolean canFinish(int numCourses, int[][] prerequisites) {
              edges = new ArrayList<List<Integer>>();
              visited = new int[numCourses];
              for(int i=0;i<numCourses;i++){
                  edges.add(new ArrayList<Integer>());
              }
              for(int i=0;i<prerequisites.length;i++){
                  edges.get(prerequisites[i][1]).add(prerequisites[i][0]);
              }
              for(int v=0;v<numCourses&&valid;v++){
                  if(visited[v]==0){
                      dfs(v);
                  }
              }
              return valid;
          }
          public void dfs(int u){
              visited[u]=1;
              for(int i :edges.get(u)){
                  if(visited[i]==0){
                      dfs(i);
                      if(!valid){
                          return ;
                      }
                  }
                  if(visited[i]==1){
                      valid=false;
                      return ;
                  }
              }
              visited[u]=2;
          }
      }
      

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

题解:

  • 前缀树, 每个位置的字母都有可能是26个的其中一个, 所以每个节点的下一个分支都有26个,且只有insert一个完整的单词时, 此时查找该单词才会是完整的单词, 不能取某一单词的前部分称之为一个单词:

    public Trie(){
        children = new Trie[26];
        isEnd=false;
    }
    
  • 每insert一个单词, 结尾的部分的isEnd设置为true:

    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;
        }
    
  • 搜索某单词前缀和搜索某单词差别只是当前位置是否为该单词的结束即isEnd是否为真:

    public boolean search(String word) {
            Trie node = searchPrefix(word);
            return node !=null && node.isEnd;
        }
        
        public boolean startsWith(String prefix) {
            return searchPrefix(prefix)!=null;
        }
    
  • 前缀搜索某一前缀只要当前节点存在且不为空即可:

    public 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;
        }
    

网站公告

今日签到

点亮在社区的每一天
去签到