序:图论模板
模板一:岛屿数量(DFS - 洪水填充)
核心记忆:看到一个'1',就把它和周围连着的所有'1'都变成'2'(表示访问过),然后岛屿数+1。
cpp
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
if (grid.empty() || grid[0].empty()) return 0;
int rows = grid.size();
int cols = grid[0].size();
int count = 0;
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j, rows, cols);
count++; // 发现一块陆地,整片岛屿都标记完了,计数+1
}
}
}
return count;
}
private:
void dfs(vector<vector<char>>& grid, int i, int j, int rows, int cols) {
// 退出条件:越界 或者 不是陆地
if (i < 0 || i >= rows || j < 0 || j >= cols || grid[i][j] != '1') {
return;
}
grid[i][j] = '2'; // 标记已访问(改成'2'或'0'都行)
// 四个方向递归
dfs(grid, i + 1, j, rows, cols);
dfs(grid, i - 1, j, rows, cols);
dfs(grid, i, j + 1, rows, cols);
dfs(grid, i, j - 1, rows, cols);
}
};
背法口诀:两层循环找'1',找到就调DFS;DFS里先判断,越界非'1'就返回;改值标记然后递归四个方向。
模板二:腐烂的橘子(多源BFS)
核心记忆:先把所有坏橘子放入队列,然后一层一层往外传播。
用for(int i = queue.size(); i > 0; i--)来控制层数(分钟)。
cpp
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int rows = grid.size();
int cols = grid[0].size();
queue<pair<int, int>> q;
int fresh = 0;
// 第一步:把所有坏橘子入队,同时统计好橘子数量
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
if (grid[i][j] == 2) {
q.push({i, j});
} else if (grid[i][j] == 1) {
fresh++;
}
}
}
if (fresh == 0) return 0; // 没有好橘子,直接返回0
int minutes = 0;
int dirs[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}}; // 四个方向
// 第二步:BFS层序遍历
while (!q.empty() && fresh > 0) {
minutes++;
int size = q.size(); // 关键:记录当前层的橘子数
for (int i = 0; i < size; i++) {
auto [r, c] = q.front();
q.pop();
for (auto& dir : dirs) {
int nr = r + dir[0];
int nc = c + dir[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
grid[nr][nc] = 2;
fresh--;
q.push({nr, nc});
}
}
}
}
return fresh == 0 ? minutes : -1;
}
};
背法口诀:先遍历,坏入队好计数;层数用size锁,四个方向去传染;传染完fresh减,最后看fresh归零没。
模板三:课程表(拓扑排序 / Kahn算法)
核心记忆:
建图(邻接表)+ 入度数组 -> 入度为0的入队 -> BFS弹出时减少邻居入度 -> 统计弹出的节点数是否等于总课程数。
cpp
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// 1. 建图:邻接表 和 入度数组
vector<vector<int>> graph(numCourses);
vector<int> indegree(numCourses, 0);
for (auto& pre : prerequisites) {
int course = pre[0];
int prerequisite = pre[1];
graph[prerequisite].push_back(course); // prerequisite -> course
indegree[course]++;
}
// 2. 将所有入度为0的节点入队
queue<int> q;
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
// 3. BFS处理
int count = 0;
while (!q.empty()) {
int node = q.front();
q.pop();
count++;
for (int neighbor : graph[node]) {
indegree[neighbor]--;
if (indegree[neighbor] == 0) {
q.push(neighbor);
}
}
}
return count == numCourses;
}
};
背法口诀:遍历先决条件建图算入度;入度为零入队列;弹出节点count加,邻居入度减减看归零;最后看count是否等于总课程。
1 题目
你这个学期必须选修 numCourses 门课程,记为 0 到 numCourses - 1 。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 ai 则 必须 先学习课程 bi 。
- 例如,先修课程对
[0, 1]表示:想要学习课程0,你需要先完成课程1。
请你判断是否可能完成所有课程的学习?如果可以,返回 true ;否则,返回 false 。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 20000 <= prerequisites.length <= 5000prerequisites[i].length == 20 <= ai, bi < numCoursesprerequisites[i]中的所有课程对 互不相同
2 代码实现
c++
cpp
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
vector <vector <int >> graph (numCourses);
vector <int> indegree (numCourses ,0 );
for (auto & pre : prerequisites){
int course = pre[0];
int prerequisites = pre[1] ;
graph[prerequisites].push_back(course);
indegree[course] ++;
}
queue <int> q ;
for (int i = 0 ; i < numCourses ; i ++){
if (indegree[i] == 0 ){
q.push(i);
}
}
int count = 0 ;
while (!q.empty()){
int node = q.front() ;
q.pop();
count ++ ;
for (int neighbor : graph[node]){
indegree[neighbor] -- ;
if (indegree [neighbor] == 0 ){
q.push(neighbor);
}
}
}
return count == numCourses ;
}
};
js
javascript
/**
* @param {number} numCourses
* @param {number[][]} prerequisites
* @return {boolean}
*/
var canFinish = function(numCourses, prerequisites) {
const graph = new Array(numCourses).fill().map(() => []);
const indegree = new Array (numCourses).fill(0);
for (const pre of prerequisites){
const course = pre [0];
const prerequisites = pre[1];
graph[prerequisites].push(course);
indegree[course]++;
}
const queue = [];
for (let i = 0 ; i < numCourses ; i++){
if (indegree[i] === 0 ){
queue.push(i);
}
}
let count = 0 ;
while (queue.length > 0 ){
const node = queue.shift();
count ++;
for (const neighbor of graph[node]){
indegree[neighbor]--;
if (indegree[neighbor] === 0 ){
queue.push(neighbor);
}
}
}
return count === numCourses ;
};
思考
这怎么做啊???????我也许只能先背诵一下。。。。
题解
cpp
class Solution {
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
// graph[课] = 学完这门课之后能解锁的课程列表
// 例如:graph[0] = [1,2] 表示学完0之后,1和2的前置条件少了一个
vector<vector<int>> graph(numCourses);
// indegree[课] = 这门课还有多少个前置条件没完成
// 例如:indegree[3] = 2 表示学3之前还需要先学2门别的课
vector<int> indegree(numCourses, 0);
// 遍历所有的先决条件,把图建好,把入度算好
for (auto& pre : prerequisites) {
int course = pre[0]; // 想学的课
int prerequisite = pre[1]; // 必须先学的课
// 从 prerequisite 指向 course:学完 prerequisite 后可以解锁 course
graph[prerequisite].push_back(course);
// course 又多了一个前置条件,所以入度+1
indegree[course]++;
}
// 队列里存放"当前可以学的课"(也就是入度为0的课)
queue<int> q;
// 把所有入度为0的课(没有前置条件的课)加入队列
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
q.push(i);
}
}
// count 记录已经学完的课程数量
int count = 0;
// 每次从队列里取一门课来"学"
while (!q.empty()) {
int node = q.front(); // 当前要学的课
q.pop();
count++; // 学完了,计数+1
// 学完 node 之后,看看它能解锁哪些课
for (int neighbor : graph[node]) {
// neighbor 这门课的前置条件少了一个(因为 node 学完了)
indegree[neighbor]--;
// 如果 neighbor 的前置条件全部学完了(入度变成0)
// 那么 neighbor 现在可以学了,加入队列
if (indegree[neighbor] == 0) {
q.push(neighbor);
}
}
}
// 如果学完的课程数量等于总课程数,说明没有环,可以全部学完
// 如果小于,说明有环(互相依赖),有些课永远学不了
return count == numCourses;
}
};
bash
输入:numCourses = 4, prerequisites = [[1,0], [2,0], [3,1], [3,2]]
执行过程:
初始:
indegree = [0, 1, 1, 2] // 课0没人依赖它,课3有两门前置课
queue = [0] // 课0可以学
第1步:学课0 → count=1 → 解锁课1和课2 → indegree变成[0,0,0,2] → 课1、课2入队
queue = [1, 2]
第2步:学课1 → count=2 → 解锁课3 → indegree变成[0,0,0,1]
queue = [2]
第3步:学课2 → count=3 → 解锁课3 → indegree变成[0,0,0,0] → 课3入队
queue = [3]
第4步:学课3 → count=4
queue = []
count(4) == numCourses(4) → 返回 true
cpp
numCourses = 4
prerequisites = [[1,0], [2,0], [3,1], [3,2]]
- 课程:0、1、2、3
- 依赖关系:
- 学 1 前先学 0
- 学 2 前先学 0
- 学 3 前先学 1 和 2
有向图结构:
0
/ \
1 2
\ /
3
预期结果:true
第 1 步:建图 + 统计入度
-
邻接表
cppgraph[0] = [1, 2] graph[1] = [3] graph[2] = [3] graph[3] = [] -
入度数组
cppindegree[0] = 0 indegree[1] = 1 indegree[2] = 1 indegree[3] = 2
第 2 步:入度为 0 的节点入队
cpp
队列 q = [0]
已修课程计数 count = 0
第 3 步:BFS 遍历
第 1 轮
- 弹出节点 0,
count = 1 - 处理邻居 1:
indegree[1]-- → 0,入队 - 处理邻居 2:
indegree[2]-- → 0,入队 - 队列:
[1, 2]
第 2 轮
- 弹出节点 1,
count = 2 - 处理邻居 3:
indegree[3]-- → 1,不入队 - 队列:
[2]
第 3 轮
- 弹出节点 2,
count = 3 - 处理邻居 3:
indegree[3]-- → 0,入队 - 队列:
[3]
第 4 轮
- 弹出节点 3,
count = 4 - 无邻居,队列变空
第 4 步:结果判断
count == numCourses → 4 == 4 → return true
cpp
初始入度:[0,1,1,2],队列:[0]
1. 弹出 0 → 1、2 入度归零 → 入队
入度:[0,0,0,2],队列:[1,2]
2. 弹出 1 → 3 入度变为 1
入度:[0,0,0,1],队列:[2]
3. 弹出 2 → 3 入度归零 → 入队
入度:[0,0,0,0],队列:[3]
4. 弹出 3 → 全部学完
count = 4,返回 true
cpp
numCourses = 2
prerequisites = [[1,0], [0,1]]
- 0 和 1 互相依赖,形成环
- 初始入度:
[1,1],无入度为 0 节点 - 队列为空,BFS 不执行,
count = 0 0 == 2 → return false
环上节点入度永远无法变为 0,不会入队,最终 count < 总课程数,以此判断有环、无法完成。
3 题目
在给定的 m x 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
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个方向上。
示例 3:
输入:grid = [[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。
提示:
m == grid.lengthn == grid[i].length1 <= m, n <= 10grid[i][j]仅为0、1或2
4 代码实现
c++
cpp
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int rows = grid.size(); // 行
int cols = grid[0].size(); //列
queue<pair<int, int >> q ;
int fresh = 0 ;
for (int i = 0 ; i < rows ; i++){
for (int j = 0 ; j < cols ; j ++){
if (grid[i][j] == 2 ){
q.push({i , j});
}else if (grid[i][j] == 1 ){
fresh ++ ;
}
}
}
if (fresh == 0 ) return 0 ;
int minutes = 0 ;
int dirs[4][2] = {{1, 0},{-1, 0},{ 0, 1},{0, -1}};
while (!q.empty()&& fresh > 0){
minutes ++ ;
int s = q.size();
for (int i = 0 ; i < s; i++){
auto[r,c] = q.front();
q.pop();
for (auto& dir :dirs){
int nr = r + dir[0];
int nc = c + dir[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
grid[nr][nc] = 2;
fresh--;
q.push({nr, nc});
}
}
}
}
return fresh == 0 ? minutes : -1 ;
}
};
js
javascript
/**
* @param {number[][]} grid
* @return {number}
*/
var orangesRotting = function(grid) {
const rows = grid.length;
const cols = grid[0].length ;
const queue = [];
let fresh = 0 ;
for (let i = 0 ; i < rows ; i ++){
for (let j = 0 ; j < cols ; j++){
if (grid[i][j] === 2 ){
queue.push([i,j]);
}else if (grid[i][j] === 1){
fresh ++ ;
}
}
}
if (fresh === 0 ) return 0 ;
let minutes = 0 ;
const dirs = [[1, 0], [-1, 0], [0, 1], [0, -1]];
while (queue.length > 0 && fresh > 0){
minutes ++;
const levelSize = queue.length ;
for (let i = 0 ; i < levelSize ; i++){
const [r ,c ] = queue.shift ();
for (const dir of dirs){
const nr = r + dir[0];
const nc = c + dir[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] === 1){
grid[nr][nc] = 2 ;
fresh -- ;
queue.push([nr,nc]);
}
}
}
}
return fresh === 0 ? minutes : -1 ;
};
思考
处理思路,如果是腐烂的橘子特殊处理,bfs四周扩散。
自己抄还抄错了
cpp
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int rows = grid.size(); // 行
int cols = grid[0].size(); //列
queue<pair<int, int >> q ;
int fresh = 0 ;
for (int i = 0 ; i < rows ; i++){
for (int j = 0 ; j < cols ; j ++){
if (grid[i][j] == 2 ){
q.push({i , j});
}else if (grid[i][j] == 1 ){
fresh ++ ;
}
}
}
if (fresh == 0 ) return 0 ;
int minutes = 0 ;
int dirs[4][2] = {{1, 0},{-1, 0},{ 0, 1},{0, -1}};
while (!q.empty()&& fresh > 0){
minutes ++ ;
for (int i = 0 ; i < q.size() ; i++){
auto[r,c] = q.front();
q.pop();
for (auto& dir :dirs){
int nr = r + dir[0];
int nc = c + dir[1];
if (nr >= 0 && nr < rows && nc >= 0 && nc < cols && grid[nr][nc] == 1) {
grid[nr][nc] = 2;
fresh--;
q.push({nr, nc});
}
}
}
}
return fresh == 0 ? minutes : -1 ;
}
};
错因
问题在于 while 循环里的 for 循环条件使用了动态变化的 q.size()。
好老掉牙的错误。哭了。
5 小结
我说实话我现在只能背诵。。。。为何会如此,好难啊!手敲了一遍什么都没过脑子。。。感觉图学得完蛋了。
bash
// 岛屿
numIslands() {
for 遍历所有格子
if 是陆地
dfs标记整片
count++
}
// 腐烂橘子
orangesRotting() {
统计坏橘子和好橘子
while(队列不空 && 有好橘子)
minutes++
int size = q.size()
for 处理这一层所有坏橘子
四个方向传染
}
// 课程表
canFinish() {
建图、统计入度
入度为0的入队
while(队列不空)
count++
for 每个邻居
indegree[邻居]--
if 变成0 then 入队
return count == numCourses
}
但是重新ai陪我看一轮我发现图论也没那么难,把一行一行代码看清楚就行了。
加油,坚持,也许睡梦中会复盘。。哈哈!