【算法】leetcode100 图论

一、岛屿数量

1、题目

200. 岛屿数量 - 力扣(LeetCode)

2、分析

  • 广度/深度优先搜索。广度优先:起始节点入队列,如果水平、垂直方向相邻位置为1切没被访问过(被访问过的1位置,直接改为0),就进队列,重复上述过程,直到队列为空。计数1个岛屿数。
  • 遍历图中每个位置为起始,搜索过或者为0的位置直接跳过,直到计数完所有岛屿。
  • 时间复杂度:循环遍历起始节点 nm,若需要搜索,图中最多搜索 nm 次。O(2nm)=O(nm)。
  • 空间复杂度:队列长度,最长为整个图都是岛屿范围,O(min(n, m))。

红色斜线是最长层:

3、代码

java 复制代码
class Solution {
    public static final int[][] position = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};

    public int numIslands(char[][] grid) {
        int sum = 0; // 计数
        int row = grid.length;
        if (row == 0) return sum; 
        int col = grid[0].length;

        // 遍历图,尝试以每个位置为起点,开始搜索
        for (int i = 0 ; i < row; i++) {
            for (int j = 0; j < col; j++) {
                // 该位置,没有被访问过且为1,才搜索
                if (grid[i][j] == '1') {
                    bfs(grid, i, j);
                    sum++;
                }
            }
        }
        return sum;
    }

    public void bfs(char[][] grid, int i, int j) {
        int row = grid.length;
        int col = grid[0].length;
        Queue<int[]> queue = new LinkedList<>();
        queue.offer(new int[]{i, j}); // 起始位置入队列
        while (!queue.isEmpty()) {
            int[] tmp = queue.poll(); // 队首出栈
            // 遍历 tmp 四个方位
            for (int[] ints : position) {
                int r = ints[0] + tmp[0];
                int c = ints[1] + tmp[1];
                // 坐标没有超过边界,且没有被遍历过,且为1,才能被纳入队列
                if (r >= 0 && r < row && c >= 0 && c < col && grid[r][c] == '1') {
                    queue.offer(new int[]{r, c});
                    grid[r][c] = '0';
                }
            }
        }
    }
}

二、腐烂的橘子

1、题目

994. 腐烂的橘子 - 力扣(LeetCode)

2、分析

  • 先找到所有腐烂橘子,从他们开始从四周腐烂(广度优先搜索新鲜橘子)。
  • 如果没有新鲜橘子,则不用腐烂,返回 0。
  • 腐烂结束后,如果还存在新鲜橘子,则返回 -1;否则返回计时。
  • 注意:最后一层腐烂橘子,四周已经没有新鲜橘子,但队列不为空,因此会进入循环,导致多计数一次。因此,循环退出条件需要添加:新鲜橘子数 <= 0。
  • 时间复杂度:找腐烂橘子、新鲜橘子 O(mn);最坏情况只有一个腐烂橘子,其他都是新鲜橘子,每个新鲜橘子都要入队、出队,O(mn)。最终:O(mn)
  • 空间复杂度:最坏情况中间都是腐烂橘子,队列长度,O(min(m, n))。

3、代码

java 复制代码
class Solution {
    public static final int[][] position = {{0, -1}, {0, 1}, {-1, 0}, {1, 0}};

    public int orangesRotting(int[][] grid) {
        int row = grid.length;
        if (row == 0) return 0;
        int col = grid[0].length;
        Queue<int[]> queue = new LinkedList<>();
        int freshCount = 0; // 统计新鲜橘子数

        for (int i = 0; i < row; i++) {
            for (int j = 0; j < col; j++) {
                // 找到初始状态中所有的腐烂橘子,放入队列,作为起始点开始搜索
                if (grid[i][j] == 2) queue.offer(new int[]{i, j});
                else if (grid[i][j] == 1) freshCount++;
            }
        }

        if (freshCount == 0) return 0; // 不存在新鲜橘子,不需要腐烂
        return bfs(grid, queue, freshCount); // 存在新鲜橘子,开始腐烂
    }

    public int bfs(int[][] grid, Queue<int[]> queue, int freshCount) {
        int row = grid.length;
        int col = grid[0].length;
        int count = 0; // 计分钟数

        // freshCount 的作用:最后一层,不会再继续腐烂,不应该计数。此时队列不为空,但 freshCount 为空,就可以结束循环
        while(!queue.isEmpty() && freshCount > 0) {
            int size = queue.size(); // 该层长度
            while (size-- != 0) {
                int[] tmp = queue.poll(); // 队首出
                // 遍历四个方位上,新鲜的橘子,让它腐烂,并计数
                for (int k = 0; k < position.length; k++) {
                    int r = tmp[0]+position[k][0];
                    int c = tmp[1]+position[k][1];
                    if (r >= 0 && r < row && c >= 0 && c < col && grid[r][c] == 1) {
                        queue.offer(new int[]{r, c});
                        grid[r][c]=2;
                        freshCount--;
                    }
                }
            }
            count++; // 该层腐烂结束后计时
        }

        return freshCount == 0 ? count : -1; // 新鲜橘子没腐烂完,返回-1
    }
}

三、课程表

1、题目

207. 课程表 - 力扣(LeetCode)

2、分析

  • 拓扑排序(针对有向无环图进行排序,让先修课程在前):先找到图中没有入度的节点,从他们开始遍历。如果某一个节点的入度边都遍历过了,就可以把该节点在图中删除,入队列,遍历其出度。
  • 时间复杂度:初始化图列表 n,构建图 m,找出起始无入度的节点 n,拓扑排序遍历边 m。O(n+m)
  • 空间复杂度:构建图的列表(结点数n+边数m)、存储入度计数的数组n、存储没有入度的节点队列n,O(n+m)。

3、代码

java 复制代码
class Solution {
    public boolean canFinish(int numCourses, int[][] prerequisites) {
         int len = prerequisites.length;
         if (len == 0) return true;
         // 构建"先修->选修"关系图,如:1->[0,2]。并统计每个节点的入度
         List<List<Integer>> map = new ArrayList<>(); 
         int[] preCnt = new int[numCourses];
         for (int i = 0; i < numCourses; i++) map.add(new ArrayList<>());

         for (int i = 0; i < len; i++) {
            List<Integer> l  ist = map.get(prerequisites[i][1]);
            list.add(prerequisites[i][0]);
            preCnt[prerequisites[i][0]]++; 
         }
         
         // 拓扑排序
         return bfs(map, preCnt, numCourses);
    }

    public boolean bfs(List<List<Integer>> map, int[] preCnt, int numCourses) {
        int cnt = 0; // 计数已经上过的课
        Deque<Integer> queue = new ArrayDeque<>();
        // 所有入度为 0 的节点入栈
        for (int i = 0; i < numCourses; i++) {
            if (preCnt[i] == 0) queue.offer(i);
        }

        // 开始拓扑排序
        while (!queue.isEmpty()) {
            int tmp = queue.pop(); // 队首
            cnt++;
            List<Integer> list = map.get(tmp); // 选修列表
            // 选修列表入度减1,并把入度为0的加入队列
            for (int value : list) {
                preCnt[value]--;
                if (preCnt[value] == 0) queue.offer(value);
            }
        }
            

        if (cnt == numCourses) return true;
        return false;
    }
}

四、实现 Trie 前缀树

1、题目

208. 实现 Trie (前缀树) - 力扣(LeetCode)

2、分析

  • 每个树节点,需要存储 26 个子节点的哈希表,若映射值为空则表示没有该子节点。
  • Trie:创建树根。
  • insert:遍历字母,从根节点开始匹配。若对应字母子节点不存在,则创建节点放入哈希表;若存在,直接移动到子节点。
  • search:遍历字母,从根节点开始匹配。存在不匹配,直接返回 false;匹配完了,但是最后一个匹配字母不是最后一个字母,返回 false;其它返回 true。
  • startsWith:遍历字母,从根节点开始匹配。存在不匹配,直接返回 false;匹配完了,返回 true。
  • 时间复杂度:除了 Trie 是 O(1),其他都是 O(word 长度) 。
  • 空间复杂度:总共开销 O(26*所有word长度)。

3、代码

java 复制代码
class TreeNode {
    public TreeNode[] children; // 节点对应 26 个小写字母的分支
    public boolean isEnd; // 标记该节点的字母是否是结尾

    TreeNode () {
        children = new TreeNode[26];
    }
}
 
class Trie {
    private TreeNode root;

    public Trie() {
        root = new TreeNode();
    }
    
    public void insert(String word) {
        TreeNode cur = root;
        for (int i = 0; i < word.length(); i++) {
            int index = word.charAt(i)-'a';
            // 该字母没有创建,则创建节点
            if (cur.children[index] == null) cur.children[index] = new TreeNode();
            // 创建了,则直接遍历下一个
            cur = cur.children[index];
        }
        cur.isEnd = true; // 标记结尾
    }
    
    public boolean search(String word) {
        TreeNode cur = root;
        for (int i = 0; i < word.length(); i++) {
            int index = word.charAt(i)-'a';
            // 没有匹配字母,返回 false
            if (cur.children[index] == null) return false;
            cur = cur.children[index];
        }
        // 如果最后一个字符是结尾节点,返回 true
        return cur.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        TreeNode cur = root;
        for (int i = 0; i < prefix.length(); i++) {
            int index = prefix.charAt(i)-'a';
            // 没有匹配字母,返回 false
            if (cur.children[index] == null) return false;
            cur = cur.children[index];
        }
        
        return true;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */
相关推荐
Xの哲學9 小时前
Linux自旋锁深度解析: 从设计思想到实战应用
linux·服务器·网络·数据结构·算法
晚风吹长发9 小时前
深入理解Linux中用户缓冲区,文件系统及inode
linux·运维·算法·链接·缓冲区·inode
cwplh9 小时前
DP 优化一:单调队列优化 DP
算法
Halo_tjn9 小时前
基于Java的相关知识点
java·开发语言·windows·python·算法
CoovallyAIHub9 小时前
英伟达CES 2026炸场:没有新显卡,却掏出了让全球AI公司彻夜难眠的“算力核弹”
深度学习·算法·计算机视觉
wregjru9 小时前
【C++】2.9异常处理
开发语言·c++·算法
CoovallyAIHub9 小时前
如何用10%的标注数据,达到可媲美全监督模型的性能?AAAI 2026论文揭秘BCSI三大创新设计
深度学习·算法·计算机视觉
肆悟先生9 小时前
3.18 constexpr函数
开发语言·c++·算法
别在内卷了10 小时前
三步搞定:双指针归并法求两个有序数组的中位数(Java 实现)
java·开发语言·学习·算法
范纹杉想快点毕业10 小时前
C语言100个经典编程练习题(完整标题+清晰排版)
运维·c语言·单片机·嵌入式硬件·算法