LeetCode Hot 100 | 图论(C++ 题解)

LeetCode Hot 100 | 图论(C++ 题解)

Hot100 图论:200 / 994 / 207 / 208


一、200. Number of Islands(岛屿数量)🟡 中等

题目描述

给你一个由 '1'(陆地)和 '0'(水)组成的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水拦截,并且每座岛屿只能由水平方向和/或垂直方向上相邻的陆地连接而成。

示例 1:

输入:grid = [
  ["1","1","1","1","0"],
  ["1","1","0","1","0"],
  ["1","1","0","0","0"],
  ["0","0","0","0","0"]
]
输出:1

示例 2:

输入:grid = [
  ["1","1","0","0","0"],
  ["1","1","0","0","0"],
  ["0","0","1","0","0"],
  ["0","0","0","1","1"]
]
输出:3

图解

200图解

解题思路

DFS 染色(本解):

遍历网格,每遇到 '1' 就发起一次 DFS,将该岛屿的所有格子标记为 '2'(已访问),同时岛屿计数 +1。

DFS 的边界条件:越界、非 '1' 时返回。通过标记 '2' 避免重复访问。

举例(示例 2):

(0,0) 是 '1' → DFS 标记 (0,0),(0,1),(1,0),(1,1) 为 '2',ans=1
(2,2) 是 '1' → DFS 标记 (2,2),ans=2
(3,3) 是 '1' → DFS 标记 (3,3),(3,4),ans=3

最终:3 ✅

代码亮点: 使用 C++23 的 this auto && dfs 语法实现 lambda 递归,写法简洁。

复杂度: 时间 O(m×n),空间 O(m×n)(递归栈)

C++ 代码

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        int rowSize = grid.size();
        int colSize = grid[0].size();
        int ans = 0;
        
        auto dfs = [&] (this auto && dfs, int row, int col) -> void {
            if (row < 0 || row >= rowSize || col < 0 || col >= colSize ||
               grid[row][col] != '1')
                return;
            
            grid[row][col] = '2';
            dfs(row, col - 1);
            dfs(row, col + 1);
            dfs(row + 1, col);
            dfs(row - 1, col);
        };
        
        for (int i = 0; i < rowSize; i++) {
            for (int j = 0; j < colSize; j++) {
                if (grid[i][j] == '1') {
                    dfs(i, j);
                    ans++;
                }
            }
        }
        
        return ans;
    }
};

二、994. Rotting Oranges(腐烂的橘子)🟡 中等

题目描述

在给定的 m × n 网格 grid 中,每个单元格可以有以下三个值之一:

  • 0 代表空单元格
  • 1 代表新鲜橘子
  • 2 代表腐烂的橘子

每分钟,腐烂的橘子周围 4 个方向上相邻的新鲜橘子都会腐烂。

返回直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1

示例 1:

输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4

示例 2:

输入:grid = [[2,1,1],[0,1,1],[1,0,1]]
输出:-1(左下角的 1 无法被感染)

示例 3:

输入:grid = [[0,2]]
输出:0(没有新鲜橘子)

图解

994图解

解题思路

多源 BFS(本解):

同时从所有腐烂橘子出发做 BFS,每一轮 BFS 相当于过一分钟。

初始化:

  • 统计所有新鲜橘子数量 fresh
  • 将所有腐烂橘子位置加入队列

BFS 过程:每轮弹出当前队列中的所有腐烂橘子,扩散到相邻新鲜橘子,将其标记为 2 并加入队列,fresh--,轮次 ans++

终止:当 fresh == 0 或队列为空时停止。若结束后 fresh > 0 说明有橘子无法腐烂,返回 -1

举例(示例 1):

初始:fresh=6,queue={(0,0)}
第1轮:(0,0)扩散→(0,1),(1,0)腐烂,fresh=4,ans=1
第2轮:(0,1),(1,0)扩散→(0,2),(1,1)腐烂,fresh=2,ans=2
第3轮:(0,2),(1,1)扩散→(2,1)腐烂,fresh=1,ans=3
第4轮:(2,1)扩散→(2,2)腐烂,fresh=0,ans=4

fresh==0,返回 4 ✅

复杂度: 时间 O(m×n),空间 O(m×n)

C++ 代码

class Solution {
public:
    int orangesRotting(vector<vector<int>>& grid) {
        int ans = 0;
        int rowSize = grid.size();
        int colSize = grid[0].size();
        queue<pair<int, int>> q;
        int fresh = 0;
        
        for (int i = 0; i < rowSize; i++) {
            for (int j = 0; j < colSize; j++) {
                if (grid[i][j] == 1)
                    fresh++;
                else if (grid[i][j] == 2)
                    q.push(make_pair(i, j));
            }
        }
        
        vector<vector<int>> dir {{1, 0}, {-1, 0}, {0, -1}, {0, 1}};
        while (fresh && !q.empty()) {
            int size = q.size();
            
            for (int j = 0; j < size; j++) {
                auto pos = q.front();
                q.pop();
                for (int i = 0; i < dir.size(); i++) {
                    int x = pos.first + dir[i][0];
                    int y = pos.second + dir[i][1];
                
                    if (x >= 0 && x < rowSize && y >= 0 && y < colSize && 
                        grid[x][y] == 1) {
                        fresh--;
                        grid[x][y] = 2;
                        q.push(make_pair(x, y));
                    }
                }
            }
            ans++;    
        }
        
        return fresh ? -1 : ans;
    }
};

三、207. Course Schedule(课程表)🟡 中等

题目描述

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi],表示如果要学习课程 ai必须先学习课程 bi

请你判断是否可能完成所有课程的学习?

示例 1:

输入:numCourses = 2, prerequisites = [[1,0]]
输出:true(先上0,再上1)

示例 2:

输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false(循环依赖)

图解

207图解

解题思路

拓扑排序(BFS Kahn 算法)(本解):

如果课程间存在循环依赖,则无法完成所有课程,等价于判断有向图是否存在环。

用拓扑排序(BFS 版本):

  1. 建图:umap[a] 存 a 的前驱,inDegree[b]++(b 有入度)
  2. 将所有入度为 0 的节点入队,计数 count
  3. BFS:每次出队一个节点,将其所有前驱的入度 -1,若入度变为 0 则入队,count++
  4. count == numCourses,说明所有节点都被处理过,无环

注意: 代码中 umap[prerequisites[i][0]].push_back(prerequisites[i][1])a → b 方向(a 依赖 b,b 是 a 的先修),inDegree[b]++ 计的是 b 被依赖的次数(b 被解锁后才能减少依赖 b 的课程的入度)。

举例([[1,0],[0,1]],循环):

inDegree = [1, 1](0和1互相依赖)
没有入度为0的节点,count=0 ≠ 2

返回 false ✅

复杂度: 时间 O(V+E),空间 O(V+E)

C++ 代码

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int, vector<int>> umap;
        vector<int> inDegre(numCourses, 0);
        int count = 0;
        queue<int> q;
        
        for (int i = 0; i < prerequisites.size(); i++) {
            umap[prerequisites[i][0]].push_back(prerequisites[i][1]);
            inDegre[prerequisites[i][1]]++;
        }
        
        for (int i =0; i < numCourses; i++) {
            if (!inDegre[i]) {
                q.push(i);
                count++;
            }
        }
        
        while (!q.empty()) {
            int courses = q.front();
            q.pop();
            vector<int> cours = umap[courses];
            for (auto cour : cours) {
                inDegre[cour]--;
                if (!inDegre[cour]){
                    q.push(cour);
                    count++;
                }
            }
        }
        
        return (count == numCourses);
    }
};

四、208. Implement Trie (Prefix Tree)(实现 Trie 前缀树)🟡 中等

题目描述

Trie(发音类似 “try”)或者说前缀树是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。

请你实现 Trie 类:

  • Trie():初始化前缀树对象
  • void insert(String word):向前缀树中插入字符串 word
  • boolean search(String word):如果 word 在前缀树中,返回 true
  • boolean startsWith(String prefix):如果之前已插入的字符串中有以 prefix 为前缀的字符串,返回 true

示例:

输入:
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]

输出:[null, null, true, false, true, null, true]

图解

208图解

解题思路

哈希表 Trie 节点(本解):

每个节点 TreeNode 包含:

  • unordered_map<char, TreeNode*> umap:子节点映射
  • bool isEnd:标记是否为某个单词的结尾

三个操作:

  • insert:从 root 出发,对每个字符,若不存在则新建节点,最后标记 isEnd = true
  • search:从 root 出发沿字符遍历,若中途找不到字符返回 false,走完后返回 cur->isEnd
  • startsWith:与 search 相同,但最后直接返回 true(不需要 isEnd)

举例(insert “apple” 后 search “app”):

insert "apple": root→a→p→p→l→e(isEnd=true)

search "app":
  root→a→p→p → cur 存在
  走完了,cur->isEnd = false(p 不是单词结尾)
  返回 false ✅

startsWith "app":
  root→a→p→p → 走完,返回 true ✅

复杂度: 时间 O(L)(L 为字符串长度),空间 O(总字符数)

C++ 代码

class Trie {
struct TreeNode {
    unordered_map<char, TreeNode *> umap;
    bool isEnd;
    
    TreeNode() {
        umap.clear();
        isEnd = false;
    }
};
    
public:
    TreeNode *root;
    
    Trie() {
        root = new TreeNode();    
    }
    
    void insert(string word) {
        TreeNode *cur = root;
        
        for (auto &c : word) {
            if (!cur->umap.count(c)) {
                cur->umap[c] = new TreeNode();
            }
            cur = cur->umap[c];
        }
        cur->isEnd = true;
    }
    
    bool search(string word) {
        TreeNode *cur = root;
        
        for (auto &c : word) {
            if (!cur->umap.count(c))
                return false;
            cur = cur->umap[c];
        }
        
        return cur->isEnd;
    }
    
    bool startsWith(string prefix) {
        TreeNode *cur = root;
        
        for (auto &c : prefix) {
            if (!cur->umap.count(c))
                return false;
            cur = cur->umap[c];
        }
        
        return true;
    }
};

/**
 * Your Trie object will be instantiated and called as such:
 * Trie* obj = new Trie();
 * obj->insert(word);
 * bool param_2 = obj->search(word);
 * bool param_3 = obj->startsWith(prefix);
 */

总结

题号题目难度核心思路时间复杂度空间复杂度
200岛屿数量🟡 中等DFS 染色,标记 ‘2’O(m×n)O(m×n)
994腐烂的橘子🟡 中等多源 BFS,逐轮扩散O(m×n)O(m×n)
207课程表🟡 中等拓扑排序(Kahn BFS)判断有无环O(V+E)O(V+E)
208实现 Trie🟡 中等哈希表 Trie 节点,isEnd 标记词尾O(L)O(总字符)

如果这篇文章对你有帮助,欢迎点赞收藏 ⭐,也欢迎在评论区交流!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值