在 m×n
网格中,每个单元格可以是:
0
表示空单元格
1
表示新鲜橘子
2
表示腐烂橘子
每分钟,腐烂橘子会感染周围4个方向的新鲜橘子。返回所有橘子腐烂所需的最少分钟数,若不可能返回 -1
。
示例输入
grid = [[2,1,1],[1,1,0],[0,1,1]]
示例输出
4
解释:第4分钟所有橘子腐烂。
核心思想:多源BFS层序遍历
同步扩散:所有腐烂橘子作为起点同时扩散,类似多个火源同时燃烧
时间计算:每层BFS对应一分钟的感染过程
终止条件:当没有新鲜橘子时停止
关键技巧:
队列初始化时加入所有腐烂橘子
用计数器 fresh
追踪剩余新鲜橘子数量
层序遍历保证时间计算的准确性
cpp
#include#include using namespace std; class Solution { public: struct Node { int x, y; Node(int _x, int _y) : x(_x), y(_y) {} }; int orangesRotting(vector >& grid) { int m = grid.size(), n = grid[0].size(); queue q; vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1}; int fresh = 0, minutes = 0; // 初始化队列并统计新鲜橘子 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 2) { q.push(Node(i, j)); // 腐烂橘子入队 } else if (grid[i][j] == 1) { fresh++; // 统计需要感染的新鲜橘子 } } } // BFS层序遍历 while (!q.empty() && fresh > 0) { int levelSize = q.size(); // 当前层的腐烂橘子数量 for (int i = 0; i < levelSize; ++i) { Node cur = q.front(); q.pop(); // 向四个方向扩散 for (int k = 0; k < 4; ++k) { int nx = cur.x + dx[k], ny = cur.y + dy[k]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) { grid[nx][ny] = 2; // 标记为腐烂 q.push(Node(nx, ny)); fresh--; // 更新新鲜橘子计数 } } } minutes++; // 每层代表一分钟 } return fresh == 0 ? minutes : -1; } };
Node结构体设计:仅需存储坐标 (x, y)
,时间由层数隐式记录
层序遍历的意义:队列的每层元素对应同一分钟被感染的橘子
空间复杂度优化:直接在原数组上修改状态,无需额外存储
时间复杂度:O(mn),每个单元格最多入队一次
示例流程:
在由 +
(墙)和 .
(路)组成的迷宫中,找到从入口到最近出口的最短路径长度。出口定义为边界上的空格子,入口不算出口。
示例输入
maze = [[".","+",".","+"],[".",".",".","+"],["+","+","+","."]], entrance = [1,2]
示例输出
1
解释:向上一步即可到达边界出口。
核心思想:单源BFS最短路径
路径探索:从入口出发,BFS天然适合找最短路径
出口判定:移动到边界时立即返回(BFS保证首次到达即最短)
状态标记:访问过的格子标记为墙防止重复访问
关键技巧:
起点入队后立即标记
步数记录在Node结构体中
边界判断优先于常规移动判断
cpp
class Solution { public: struct Node { int x, y, dist; Node(int _x, int _y, int _d) : x(_x), y(_y), dist(_d) {} }; int nearestExit(vector>& maze, vector & entrance) { int m = maze.size(), n = maze[0].size(); vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1}; queue q; // 起点入队并标记 q.push(Node(entrance[0], entrance[1], 0)); maze[entrance[0]][entrance[1]] = '+'; while (!q.empty()) { Node cur = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int nx = cur.x + dx[k], ny = cur.y + dy[k]; // 边界检查 if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue; if (maze[nx][ny] == '.') { // 到达出口条件 if (nx == 0 || nx == m-1 || ny == 0 || ny == n-1) { return cur.dist + 1; } maze[nx][ny] = '+'; // 标记已访问 q.push(Node(nx, ny, cur.dist + 1)); } } } return -1; } };
Node结构体设计:dist
字段记录到达当前坐标的步数
即时出口判断:在移动前判断新坐标是否在边界
空间复杂度:O(mn),最坏情况需要存储所有位置
剪枝优化:提前标记已访问节点,防止重复入队
路径可视化:
初始迷宫: + + . + . . . + + + + . BFS扩散过程: 第0步:(1,2) 第1步:到达(0,2)
在 0-1
矩阵中找到离所有陆地最远的海洋单元格的曼哈顿距离。
示例输入
grid = [[1,0,1],[0,0,0],[1,0,1]]
示例输出
2
解释:中心海洋到最近陆地的距离是2。
核心思想:多源BFS逆向传播
逆向思维:所有陆地作为起点向海洋扩散
距离计算:每个海洋单元格记录最近的陆地距离
最大值追踪:维护全局最大距离
关键技巧:
使用二维数组 dist
记录距离
初始时将陆地距离设为0
BFS传播时更新海洋单元格距离
cpp
class Solution { public: struct Node { int x, y, dist; Node(int _x, int _y, int _d) : x(_x), y(_y), dist(_d) {} }; int maxDistance(vector>& grid) { int m = grid.size(), n = grid[0].size(); queue q; vector > dist(m, vector (n, -1)); vector dx = {1, 0, -1, 0}, dy = {0, 1, 0, -1}; int max_dist = -1; // 初始化陆地 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (grid[i][j] == 1) { q.push(Node(i, j, 0)); dist[i][j] = 0; } } } // BFS传播距离 while (!q.empty()) { Node cur = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int nx = cur.x + dx[k], ny = cur.y + dy[k]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && dist[nx][ny] == -1) { dist[nx][ny] = cur.dist + 1; max_dist = max(max_dist, dist[nx][ny]); q.push(Node(nx, ny, dist[nx][ny])); } } } return max_dist; } };
Node结构体设计:dist
表示当前单元格到最近陆地的距离
距离传播特性:BFS保证每个海洋单元格首次被访问时即为最短距离
时间复杂度:O(mn),每个单元格处理一次
空间优化:复用输入矩阵标记陆地,但使用独立 dist
数组更清晰
算法演示:
初始状态: 1 0 1 0 0 0 1 0 1 距离传播过程: 0 1 0 1 2 1 0 1 0 最大距离2出现在中心
给定水域(1)和陆地(0),为陆地安排高度,满足:
水域高度为0
相邻格子高度差≤1
求可能的最大高度矩阵。
示例输入
isWater = [[0,1],[0,0]]
示例输出
[[1,0],[2,1]]
核心思想:多源BFS高度传播
水域初始化:所有水域高度0作为起点
层序递增:每层BFS高度+1
最优性保证:BFS确保每个陆地获得最小可能高度
关键技巧:
使用 ans
矩阵同时记录高度和访问状态
队列中存储当前高度值
高度差约束天然满足BFS传播特性
cpp
class Solution { public: struct Node { int x, y, val; Node(int _x, int _y, int _v) : x(_x), y(_y), val(_v) {} }; vector> highestPeak(vector >& isWater) { int m = isWater.size(), n = isWater[0].size(); vector > ans(m, vector (n, -1)); queue q; vector dx = {1,0,-1,0}, dy = {0,1,0,-1}; // 水域初始化 for (int i = 0; i < m; ++i) { for (int j = 0; j < n; ++j) { if (isWater[i][j] == 1) { ans[i][j] = 0; q.push(Node(i, j, 0)); } } } // BFS传播高度 while (!q.empty()) { Node cur = q.front(); q.pop(); for (int k = 0; k < 4; ++k) { int nx = cur.x + dx[k], ny = cur.y + dy[k]; if (nx >= 0 && nx < m && ny >= 0 && ny < n && ans[nx][ny] == -1) { ans[nx][ny] = cur.val + 1; q.push(Node(nx, ny, ans[nx][ny])); } } } return ans; } };
Node结构体设计:val
记录当前单元格的高度值
高度传播特性:相邻单元格高度差正好为1,满足题目约束
正确性证明:BFS队列按高度顺序处理,保证每个单元格首次被访问时得到最小高度
复杂度分析:时间复杂度O(mn),空间复杂度O(mn)
传播过程示例:
初始水域: 0 1 0 0 第1层传播: 1 0 1 1 第2层传播: 1 0 2 1
从矩阵左上角到右下角,找到一条路径使得相邻格子的高度差最大值最小。
示例输入
heights = [[1,2,2],[3,8,2],[5,3,5]]
示例输出
2
解释:路径 [1→3→5→3→5] 的最大高度差为2。
核心思想:优先队列优化的Dijkstra算法
路径评估:维护路径中的最大高度差
优先队列:总是扩展当前最优路径
松弛操作:当发现更优路径时更新距离
关键技巧:
使用小顶堆按当前最大高度差排序
距离矩阵 dist
记录到达各点的最小消耗
及时剪枝非最优路径
cpp
#include#include using namespace std; class Solution { public: struct Node { int x, y, max_diff; Node(int _x, int _y, int _d) : x(_x), y(_y), max_diff(_d) {} bool operator<(const Node& o) const { return max_diff > o.max_diff; // 小顶堆 } }; int minimumEffortPath(vector >& heights) { int m = heights.size(), n = heights[0].size(); vector > dist(m, vector (n, INT_MAX)); priority_queue pq; // 起点初始化 dist[0][0] = 0; pq.push(Node(0, 0, 0)); vector dx = {1,0,-1,0}, dy = {0,1,0,-1}; while (!pq.empty()) { Node cur = pq.top(); pq.pop(); // 到达终点直接返回 if (cur.x == m-1 && cur.y == n-1) return cur.max_diff; // 剪枝:当前路径已不是最优 if (cur.max_diff > dist[cur.x][cur.y]) continue; for (int k = 0; k < 4; ++k) { int nx = cur.x + dx[k], ny = cur.y + dy[k]; if (nx >= 0 && nx < m && ny >= 0 && ny < n) { int new_diff = max(cur.max_diff, abs(heights[nx][ny] - heights[cur.x][cur.y])); if (new_diff < dist[nx][ny]) { dist[nx][ny] = new_diff; pq.push(Node(nx, ny, new_diff)); } } } } return -1; // 实际不会执行到这里 } };
Node结构体设计:max_diff
记录路径中的最大高度差,运算符重载实现小顶堆
Dijkstra特性:保证首次到达终点时即为最优解
时间复杂度:O(mn log(mn)),优先队列操作主导
空间优化:可改用曼哈顿距离启发式搜索加速
路径选择示例:
复制
高度矩阵: 1 2 2 3 8 2 5 3 5 最优路径选择: 1 → 3 → 5 → 3 → 5 最大高度差:3→5(差2)
通过五道经典题目,我们看到了Node结构体的灵活应用:
题目类型 | Node结构体成员 | 设计要点 | 核心作用 |
---|---|---|---|
多源同步扩散 | x, y |
最小信息原则 | 坐标定位 |
单源最短路径 | x, y, dist |
携带路径长度 | 步数记录 |
多源距离传播 | x, y, dist |
携带传播值 | 距离计算 |
数值传播 | x, y, val |
携带传播数值 | 高度传递 |
最优路径选择 | x, y, max_diff + 排序 |
携带评估值并支持优先队列 | 智能路径选择 |
核心启示:
按需定制:根据问题需求选择携带的附加信息
算法适配:通过成员设计适配不同BFS变种(普通队列/优先队列)
性能平衡:在信息携带量和计算效率之间寻找平衡点
掌握Node结构体的灵活设计,BFS算法就能如同瑞士军刀般应对各种场景挑战。理解数据结构与算法的内在联系,是提升问题解决能力的关键。
欢迎在评论区留下你的思考!如果觉得文章有帮助,请点个赞支持一下~