《数据机构与算法》 图论 专题训练:网格图
前言
该题单由灵茶山艾府提供,在此致谢每一位无私奉献的计算机爱好者。
题单链接:https://leetcode.cn/circle/discuss/YiXPXW/
梳理
重点
- DFS
- BFS
- 并查集
难点
- 多源BFS
- 带权并查集
- 综合其它算法的应用
问题
- 如何思考带限制条件的网格图的搜索方案?
若是要在一个范围内寻找一个最优解,可以考虑图上二分查找或三分查找(选择二分还是三分查找取决于二元方程式是一次还是二次方程);若是涉及连通性,可以考虑(排序后)使用并查集;若是涉及一块连通分量的值有关的,可以简单考虑DFS/BFS;若是涉及多块连通分量的值,则可以考虑带权并查集;如果限定了某些方向,满足状态转移的情况下可以考虑使用动态规划。
- 对于网格图的搜索,如何选择恰当的算法(BFS或DFS)进行解决?
对于只需要寻找一个可行方案的情况,通常考虑使用DFS;对于需要在多个可行方案中进行选取的情况,通常考虑使用BFS。
- 当朴素的搜索算法无法满足要求时,如何思考进一步的优化?
DFS和BFS本质来讲,其实就是暴力,对于暴力算法来说,时间和空间的开销都是比较大的。那么,首先可以考虑进行状态压缩,这可以减小空间开销;其次要学会剪枝,在较大的网格图中进行BFS,也可以优化为双向BFS,这可以减小时间开销;最后,若是带有一些特别的限制条件,可以考虑综合使用其它算法。
- 在边长均相等的图中,为什么BFS可以寻找出一条最短路径,对访问过的节点进行标记是可行的呢?
因为在访问到一个节点时,若该节点已被标记为访问过,则说明一定存在一条更短的路径可以抵达当前的节点。
网格图DFS
LeetCode200 岛屿数量
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int row, column;
public:
inline bool check(int &r, int &c, vector<vector<char>> &grid) {
return r >= 0 && r < row && c >= 0 && c < column && grid[r][c] == '1';
}
void dfs(vector<vector<char>> &grid, int r, int c) {
grid[r][c] = '0';
for (int i = 0; i < 4; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
if (check(_r, _c, grid)) {
dfs(grid, _r, _c);
}
}
}
int numIslands(vector<vector<char>>& grid) {
int ans = 0;
row = grid.size(), column = grid[0].size();
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (grid[i][j] == '1') {
++ ans;
dfs(grid, i, j);
}
}
}
return ans;
}
};
LeetCode695 岛屿的最大面积
class Solution {
private:
int x[4] = {-1, 1, 0, 0}, y[4] = {0, 0, -1, 1};
int row, column;
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans = 0;
row = grid.size(), column = grid[0].size();
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (grid[i][j]) {
ans = max(ans, dfs(grid, i, j));
}
}
}
return ans;
}
inline bool check(int &r, int &c, vector<vector<int>>& grid) {
return r >= 0 && r < row && c >= 0 && c < column && grid[r][c];
}
int dfs(vector<vector<int>>& grid, int r, int c) {
int res = 1;
grid[r][c] = 0;
for (int i = 0; i < 4; ++ i) {
int _r = r + x[i], _c = c + y[i];
if (check(_r, _c, grid)) {
res += dfs(grid, _r, _c);
}
}
return res;
}
};
面试题16.19 水域大小
class Solution {
private:
int dx[8] = {-1, -1, -1, 0, 1, 1, 1, 0};
int dy[8] = {-1, 0, 1, 1, 1, 0, -1, -1};
int row, column;
public:
vector<int> pondSizes(vector<vector<int>>& land) {
vector<int> ans;
row = land.size(), column = land[0].size();
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (!land[i][j]) {
ans.emplace_back(dfs(land, i, j));
}
}
}
sort(ans.begin(), ans.end());
return ans;
}
int dfs(vector<vector<int>>& land, int r, int c) {
int res = land[r][c] = 1;
for (int i = 0; i < 8; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
if (_r >= 0 && _c >= 0 && _r < row && _c < column && !land[_r][_c]) {
res += dfs(land, _r, _c);
}
}
return res;
}
};
LeetCode463 岛屿的周长
int islandPerimeter(int** grid, int gridSize, int* gridColSize){
int i, j;
int sum = 0;
for (i = 0; i < gridSize; i ++) {
for (j = 0; j < *gridColSize; j ++) {
if (grid[i][j] == 1) {
sum += 4;
// a[i][j] = 0;
if (i > 0 && grid[i - 1][j] == 1) {
sum -= 1;
}
if (i <= gridSize - 2 && grid[i + 1][j] == 1) {
sum -= 1;
}
if (j > 0 && grid[i][j - 1] == 1) {
sum -= 1;
}
if (j <= *gridColSize - 2 && grid[i][j + 1] == 1) {
sum -= 1;
}
}
}
}
return sum;
}
LeetCode2658 网格图中鱼的最大数目
class Solution {
private:
int x[4] = {-1, 1, 0, 0}, y[4] = {0, 0, -1, 1};
int row, column;
public:
int findMaxFish(vector<vector<int>>& grid) {
int ans = 0;
row = grid.size(), column = grid[0].size();
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (grid[i][j]) {
ans = max(ans, dfs(grid, i, j));
}
}
}
return ans;
}
inline bool check(int &r, int &c, vector<vector<int>>& grid) {
return r >= 0 && r < row && c >= 0 && c < column && grid[r][c];
}
int dfs(vector<vector<int>>& grid, int r, int c) {
int res = grid[r][c];
grid[r][c] = 0;
for (int i = 0; i < 4; ++ i) {
int _r = r + x[i], _c = c + y[i];
if (check(_r, _c, grid)) {
res += dfs(grid, _r, _c);
}
}
return res;
}
};
LeetCode1034 边界着色
class Solution {
private:
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int R, C;
public:
vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col, int color) {
R = grid.size(), C = grid[0].size();
vector<vector<bool>> vis(R, vector<bool>(C, true));
dfs(grid, vis, row, col, color);
return grid;
}
void dfs(vector<vector<int>>& grid, vector<vector<bool>> &vis, int row, int col, int color) {
bool need = !row || !col || row == R - 1 || col == C - 1;
vis[row][col] = false;
for (int i = 0; i < 4; ++ i) {
int r = row + dx[i], c = col + dy[i];
if (r >= 0 && r < R && c >= 0 && c < C && vis[r][c]) {
if (grid[r][c] == grid[row][col]) {
dfs(grid, vis, r, c, color);
} else {
need = true;
}
}
}
if (need) {
grid[row][col] = color;
}
}
};
LeetCode1020 飞地的数量
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int row, column;
public:
int numEnclaves(vector<vector<int>>& grid) {
int ans = 0;
row = grid.size(), column = grid[0].size();
for (int i = 0; i < row; ++ i) {
if (grid[i][0]) {
dfs(grid, i, 0);
}
if (grid[i][column - 1]) {
dfs(grid, i, column - 1);
}
}
for (int i = 0; i < column; ++ i) {
if (grid[0][i]) {
dfs(grid, 0, i);
}
if (grid[row - 1][i]) {
dfs(grid, row - 1, i);
}
}
for (int i = 1; i < row - 1; ++ i) {
for (int j = 1; j < column - 1; ++ j) {
if (grid[i][j]) {
++ ans;
}
}
}
return ans;
}
void dfs(vector<vector<int>> &grid, int r, int c) {
grid[r][c] = 0;
for (int i = 0; i < 4; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
if (_r >= 0 && _r < row && _c >= 0 && _c < column && grid[_r][_c]) {
dfs(grid, _r, _c);
}
}
}
};
LeetCode1254 统计封闭岛屿的数目
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int row, column;
public:
int closedIsland(vector<vector<int>>& grid) {
int ans = 0;
row = grid.size(), column = grid[0].size();
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (grid[i][j] == 0 && dfs(grid, i, j)) {
++ ans;
}
}
}
return ans;
}
bool check(int r, int c) {
return r >= 0 && r < row && c >= 0 && c < column;
}
bool dfs(vector<vector<int>>& grid, int r, int c) {
bool res = r != 0 && c != 0 && r != (row - 1) && c != (column - 1);
grid[r][c] = 1;
for (int i = 0; i < 4; ++ i) {
int new_r = r + dx[i], new_c = c + dy[i];
if (check(new_r, new_c) && !grid[new_r][new_c]) {
bool b = dfs(grid, new_r, new_c);
res = res && b;
}
}
return res;
}
};
LeetCode130 被围绕的区域
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int row, column;
public:
void solve(vector<vector<char>>& board) {
row = board.size(), column = board[0].size();
vector<vector<bool>> flag(row, vector<bool>(column, true));
for (int i = 0; i < row; ++ i) {
if (board[i][0] == 'O' && flag[i][0]) dfs(board, flag, i, 0);
if (board[i][column - 1] == 'O' && flag[i][column - 1]) dfs(board, flag, i, column - 1);
}
for (int i = 1; i < column - 1; ++ i) {
if (board[0][i] == 'O' && flag[0][i]) dfs(board, flag, 0, i);
if (board[row - 1][i] == 'O' && flag[row - 1][i]) dfs(board, flag, row - 1, i);
}
for (int i = 1; i < row - 1; ++ i) {
for (int j = 1; j < column - 1; ++ j) {
if (flag[i][j]) board[i][j] = 'X';
}
}
}
void dfs(vector<vector<char>>& board, vector<vector<bool>>& flag, int r, int c) {
flag[r][c] = false;
for (int i = 0; i < 4; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
if (_r >= 0 && _r < row && _c >= 0 && _c < column && board[_r][_c] == 'O' && flag[_r][_c]) {
dfs(board, flag, _r, _c);
}
}
}
};
LeetCode1391 检查网格中是否存在有效路径
class Solution {
//垃圾代码...建议去看评论区大佬的代码...
private:
constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m, n;
public:
int getDirection(int dir, int streetType) {
switch(dir) {
case 0://上
switch(streetType) {
case 1:
case 5:
case 6:
dir = -1;
break;
case 3:
dir = 2;
break;
case 4:
dir = 3;
break;
}
break;
case 1://下
switch(streetType) {
case 1:
case 3:
case 4:
dir = -1;
break;
case 5:
dir = 2;
break;
case 6:
dir = 3;
break;
}
break;
case 2://左
switch(streetType) {
case 2:
case 3:
case 5:
dir = -1;
break;
case 4:
dir = 1;
break;
case 6:
dir = 0;
break;
}
break;
case 3://右
switch(streetType) {
case 2:
case 4:
case 6:
dir = -1;
break;
case 3:
dir = 1;
break;
case 5:
dir = 0;
break;
}
break;
}
return dir;
}
void dfs(vector<vector<int>>& grid, int r, int c, int dir, bool &ans) {
//dir:上0 下1 左2 右3
if (r < 0 || r >= m || c < 0 || c >= n || grid[r][c] == -1) return ;
int nextDir = getDirection(dir, grid[r][c]);
if (nextDir == -1) return ;
int val = grid[r][c];
grid[r][c] = -1;
if (r == m - 1 && c == n - 1) {
ans = true;
return ;
}
dfs(grid, r + DIR[nextDir][0], c + DIR[nextDir][1], nextDir, ans);
grid[r][c] = val;
}
bool hasValidPath(vector<vector<int>>& grid) {
m = grid.size(), n = grid[0].size();
bool ans = m == 1 && n == 1;
switch (grid[0][0]) {
case 1:
case 6:
dfs(grid, 0, 1, 3, ans);
break;
case 2:
case 3:
dfs(grid, 1, 0, 1, ans);
break;
case 4:
dfs(grid, 1, 0, 1, ans);
if (!ans) dfs(grid, 0, 1, 3, ans);
}
return ans;
}
};
LeetCode417 太平洋大西洋水流问题
class Solution {
private:
constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
constexpr static int TARGET = 3;//1 | 2
public:
vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
int n = heights.size(), m = heights[0].size();
vector<vector<int>> vis(n, vector<int>(m, 0));
vector<vector<int>> ans;
vector<int> r, c;
auto bfs = [&](int x) {
int front = 0, rear, tail;
while (front < (rear = r.size())) {
tail = rear;
while (front < tail) {
int row = r[front], column = c[front];
if (vis[row][column] == TARGET) {
vector<int> v = {row, column};
ans.emplace_back(v);
}
++ front;
for (int i = 0; i < 4; ++ i) {
int nr = DIR[i][0] + row, nc = DIR[i][1] + column;
if (nr >= 0 && nr < n && nc >= 0 && nc < m && vis[nr][nc] < x && heights[nr][nc] >= heights[row][column]) {
r.emplace_back(nr), c.emplace_back(nc);
vis[nr][nc] |= x;
}
}
}
}
};
for (int i = 0; i < m; ++ i) {
r.emplace_back(0), c.emplace_back(i);
vis[0][i] = 1;
}
for (int i = 1; i < n; ++ i) {
r.emplace_back(i), c.emplace_back(0);
vis[i][0] = 1;
}
bfs(1);
r.clear(), c.clear();
for (int i = 0; i < n; ++ i) {
r.emplace_back(i), c.emplace_back(m - 1);
vis[i][m - 1] |= 2;
}
for (int i = 0; i < m - 1; ++ i) {
r.emplace_back(n - 1), c.emplace_back(i);
vis[n - 1][i] |= 2;
}
bfs(2);
return ans;
}
};
LeetCode529 扫雷游戏
class Solution {
private:
constexpr static int DIR[8][2] = {{-1, -1}, {-1, 0}, {-1, 1}, {0, 1}, {1, 1}, {1, 0}, {1, -1}, {0, -1}};
public:
vector<vector<char>> updateBoard(vector<vector<char>>& board, vector<int>& click) {
int row = board.size(), column = board[0].size();
for (int i = 0; i < click.size(); i += 2) {
int x = click[i], y = click[i + 1];
if (board[x][y] == 'M') {
board[x][y] = 'X';
} else if (board[x][y] == 'E') {
board[x][y] = 'B';
vector<int> nextClick;
for (int j = 0; j < 8; ++ j) {
int nex = x + DIR[j][0], ney = y + DIR[j][1];
if (nex >= 0 && nex < row && ney >= 0 && ney < column) {
if (board[nex][ney] == 'M' || board[nex][ney] == 'X') {
if (board[x][y] == 'B') {
board[x][y] = '1';
} else {
board[x][y] += 1;
}
} else {
nextClick.emplace_back(nex), nextClick.emplace_back(ney);
}
}
}
if (!isdigit(board[x][y])) {
click.insert(click.end(), nextClick.begin(), nextClick.end());
}
}
}
return board;
}
};
LeetCode1559 二维网格图中探测环
class Solution {
private:
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int row, column;
char ch;
public:
void dfs(vector<vector<char>>& grid, vector<vector<bool>> &v, int x, int y, int len, bool &flag) {
v[x][y] = true;
int cnt = 0;
for (int i = 0; i < 4; ++ i) {
int _x = x + dx[i], _y = y + dy[i];
if (_x >= 0 && _x < row && _y >= 0 && _y < column && grid[_x][_y] == ch) {
if (len >= 3 && v[_x][_y] && ++ cnt >= 2) {
flag = true;
return ;
}
if (!v[_x][_y]) dfs(grid, v, _x, _y, len + 1, flag);
if (flag) return ;
}
}
}
bool containsCycle(vector<vector<char>>& grid) {
bool ans = false;
row = grid.size(), column = grid[0].size();
vector<vector<bool>> vis(row, vector<bool>(column));
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (!vis[i][j]) {
ch = grid[i][j];
dfs(grid, vis, i, j, 1, ans);
if (ans) return true;
}
}
}
return false;
}
};
LeetCode827 最大人工岛
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
int row, column;
vector<int> bcj, sz;
public:
int find(int x) {
if (x != bcj[x]) return bcj[x] = find(bcj[x]);
return x;
}
void merge(int x, int y) {
x = find(x), y = find(y);
if (x == y) return ;
sz[x] += sz[y];
bcj[y] = x;
}
int largestIsland(vector<vector<int>>& grid) {
auto get = [&](int i, int j) -> int {
int res = 1;
unordered_set<int> st;
for (int k = 0; k < 4; ++ k) {
int r = i + dx[k], c = j + dy[k];
if (check(r, c)) {
int key = row * r + c;
int val = find(key);
if (st.find(val) == st.end()) {
res += sz[val];
st.insert(val);
}
}
}
return res;
};
int ans = 0;
row = grid.size(), column = grid[0].size();
bcj = vector<int>(row * column);
sz = vector<int>(row * column);
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
int k = i * row + j;
bcj[k] = k;
sz[k] = grid[i][j];
}
}
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (grid[i][j]) {
dfs(grid, i, j);
ans = max(ans, sz[i * row + j]);
}
}
}
for (int i = 0; i < row; ++ i) {
for (int j = 0; j < column; ++ j) {
if (!grid[i][j]) {
ans = max(ans, get(i, j));
}
}
}
return ans;
}
bool check(int r, int c) {
return r >= 0 && r < row && c >= 0 && c < column;
}
void dfs(vector<vector<int>> &grid, int r, int c) {
grid[r][c] = 0;
int p = r * row + c;
for (int i = 0; i < 4; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
if (check(_r, _c) && grid[_r][_c] == 1) {
merge(_r * row + _c, p);
dfs(grid, _r, _c);
}
}
grid[r][c] = 2;
}
};
网格图BFS
LeetCode521 01矩阵
class Solution {
private:
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
public:
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int r = mat.size(), c = mat[0].size(), front = 0, rear, tail, dep = 1;
vector<vector<bool>> vis(r, vector<bool>(c));
vector<pair<int, int>> vec;//存储访问过的节点(i, j),本质为队列
for (int i = 0; i < r; ++ i) {
for (int j = 0; j < c; ++ j) {
if (!mat[i][j]) {//将所有0存储到vec,用于多源BFS
vec.emplace_back(make_pair(i, j));//存储节点
vis[i][j] = true;//标记为已访问
}
}
}
rear = vec.size();
while (front < rear) {//多源BFS
tail = rear;
while (front < tail) {
int x = vec[front].first, y = vec[front].second;
for (int i = 0; i < 4; ++ i) {
int _x = x + dx[i], _y = y + dy[i];
if (_x < 0 || _x >= r || _y < 0 || _y >= c) continue;
if (vis[_x][_y] == false) {//尚未访问过
mat[_x][_y] = dep;
vis[_x][_y] = true;
vec.emplace_back(make_pair(_x, _y));
}
}
++ front;
}
++ dep;//距离加1
rear = vec.size();
}
return mat;
}
};
LeetCode994 糜烂的橘子
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int m = grid.size(), n = grid[0].size(), ans = 0, cnt = 0;
vector<int> row, column;
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < n; ++ j) {
if (grid[i][j]) ++ cnt;
if (grid[i][j] == 2) row.emplace_back(i), column.emplace_back(j);
}
}
for (int front = 0, rear = row.size(), tail; front < rear; rear = row.size()) {
tail = rear;
while (front < tail) {
int r = row[front], c = column[front];
++ front;
for (int i = 0; i < 4; ++ i) {
int x = r + dx[i], y = c + dy[i];
if (x >= 0 && x < m && y >= 0 && y < n && grid[x][y] == 1) {
grid[x][y] = 2;
row.emplace_back(x), column.emplace_back(y);
}
}
}
if (tail < row.size()) ++ ans;
}
if (cnt != row.size()) return -1;
return ans;
}
};
LeetCode2684 矩阵中移动的最大次数
class Solution {
private:
int row, column;
public:
/*
假设从(r, c)可以走到(r, c + 1)
也可以从(r + 1, c + 1)走到(r, c + 1)
那么我们只需要走一次(r, c + 1)即可
为什么?
因为从(r, c + 1)能走到的最远距离,必定是相同的
因此同一个右侧结点,最多只需要走一次即可
*/
void dfs(int r, int c, int len, int &mx, vector<vector<int>> &grid) {
if (len > mx) mx = len;
if (c + 1 == column) return ;
bool flag = false;
for (int i = -1; i <= 1; ++ i) {
if (r + i >= 0 && r + i < row && grid[r][c] < grid[r + i][c + 1]) {
dfs(r + i, c + 1, len + 1, mx, grid);
flag = true;
}
}
if (flag) grid[r][c] = 0;
}
int maxMoves(vector<vector<int>>& grid) {
row = grid.size(), column = grid[0].size();
int ans = 0;
for (int i = 0; i < row; ++ i) {
dfs(i, 0, 0, ans, grid);
}
return ans;
}
};
LeetCode1926 迷宫中离入口最近的出口
class Solution {
private:
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, 1, -1};
public:
int nearestExit(vector<vector<char>>& maze, vector<int>& entrance) {
int m = maze.size(), n = maze[0].size(), mi = 0;
vector<int> r, c;
r.emplace_back(entrance[0]), c.emplace_back(entrance[1]);
maze[entrance[0]][entrance[1]] = '+';
int front = 0, rear = 1, tail;
while (front < rear) {
tail = rear;
++ mi;
while (front < tail) {
for (int i = 0; i < 4; ++ i) {
int x = r[front] + dx[i], y = c[front] + dy[i];
if (x < m && x >= 0 && y >= 0 && y < n && maze[x][y] != '+') {
maze[x][y] = '+';
if (x == 0 || x == m - 1 || y == 0 || y == n - 1) {
return mi;
}
r.emplace_back(x), c.emplace_back(y);
}
}
++ front;
}
rear = r.size();
}
return -1;
}
};
LeetCode1162 地图分析
class Solution {
public:
int maxDistance(vector<vector<int>>& grid) {
int dx[4] = {-1, 1, 0, 0}, dy[4] = {0, 0, -1, 1};
int n = grid.size();
int ans = 0;
vector<int> r, c;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
if (grid[i][j]) {
r.emplace_back(i), c.emplace_back(j);
}
}
}
if (r.size() == 0 || r.size() == n * n) {
return -1;
}
int front = 0, rear = r.size(), tail;
while (front < rear) {
tail = rear;
while (front < tail) {
for (int i = 0; i < 4; ++ i) {
int x = r[front] + dx[i], y = c[front] + dy[i];
if (x >= 0 && x < n && y >= 0 && y < n && grid[x][y] == 0) {
grid[x][y] = 1;
r.emplace_back(x), c.emplace_back(y);
}
}
++ front;
}
rear = r.size();
if (tail < rear) ++ ans;
}
return ans;
}
};
LeetCode934 最短的桥
class Solution {
/*
先dfs找出一个岛的四周的全部的0
访问过程中,用2标记已经访问过的结点
然后从这些0进行多源BFS
BFS遇到1,就是遇到另一个岛屿
*/
private:
int dx[4] = {1, -1, 0, 0}, dy[4] = {0, 0, 1, -1};
int n;
vector<pair<int, int>> vec;
public:
void dfs(vector<vector<int>>& grid, int r, int c) {
for (int i = 0; i < 4; ++ i) {
int x = r + dx[i], y = c + dy[i];
if (x >= 0 && x < n && y >= 0 && y < n) {
if (grid[x][y] == 1) {
grid[x][y] = 2;
dfs(grid, x, y);
} else if (grid[x][y] == 0) {
grid[x][y] = 2;
vec.emplace_back(make_pair(x, y));
}
}
}
}
int shortestBridge(vector<vector<int>>& grid) {
n = grid.size();
int ans = 1;
bool flag = false;
for (int i = 0; i < n; ++ i) {
for (int j = 0; j < n; ++ j) {
if (grid[i][j] == 1) {
grid[i][j] = 2;
dfs(grid, i, j);
flag = true;
break;
}
}
if (flag) {
break;
}
}
int fro = 0, rear = vec.size(), tail;
while (fro < rear) {
tail = rear;
while (fro < tail) {
for (int i = 0; i < 4; ++ i) {
int x = vec[fro].first + dx[i], y = vec[fro].second + dy[i];
if (x >= 0 && x < n && y >= 0 && y < n) {
if (grid[x][y] == 1) {
return ans;
} else if (!grid[x][y]) {
vec.emplace_back(make_pair(x, y));
grid[x][y] = 2;
}
}
}
fro ++;
}
rear = vec.size();
if (tail < rear) ++ ans;
}
return -1;
}
};
LeetCode2146 价格范围内最高排名的k样物品
class Solution {
public:
typedef tuple<int, int, int> TP;
constexpr static int DIR[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
vector<vector<int>> highestRankedKItems(vector<vector<int>>& grid, vector<int>& pricing, vector<int>& start, int k) {
vector<vector<int>> ans;
priority_queue<TP, vector<TP>, greater<TP>> pq;
int front = 0, rear, tail, m = grid.size(), n = grid[0].size(), low = pricing[0], high = pricing[1];
vector<int> r, c;
r.emplace_back(start[0]), c.emplace_back(start[1]);
if (grid[start[0]][start[1]] > 1) {
if (grid[start[0]][start[1]] >= low && grid[start[0]][start[1]] <= high) {
vector<int> res = {start[0], start[1]};
ans.emplace_back(res);
-- k;
}
grid[start[0]][start[1]] = 0;
}
auto check = [&](int row, int col) -> bool {
if (row < 0 || row >= m || col < 0 || col >= n || !grid[row][col]) {
return false;
}
return true;
};
while (front < (rear = r.size())) {
while (!pq.empty() && k -- > 0) {
vector<int> res;
TP tp = pq.top();
pq.pop();
res.emplace_back(get<1>(tp)), res.emplace_back(get<2>(tp));
ans.emplace_back(res);
}
if (k <= 0) break;
int tail = rear;
while (front < tail) {
int row = r[front], col = c[front ++];
for (int i = 0; i < 4; ++ i) {
int ner = row + DIR[i][0], nec = col + DIR[i][1];
if (check(ner, nec)) {
r.emplace_back(ner), c.emplace_back(nec);
if (grid[ner][nec] >= low && grid[ner][nec] <= high) {
pq.push(TP(grid[ner][nec], ner, nec));
}
grid[ner][nec] = 0;
}
}
}
}
return ans;
}
};
LeetCode1293 网格中的最短路径
struct Nagato {
int x, y;
int rest;
Nagato(int _x, int _y, int _r): x(_x), y(_y), rest(_r) {}
};
class Solution {
private:
static constexpr int dirs[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public:
int shortestPath(vector<vector<int>>& grid, int k) {
int m = grid.size(), n = grid[0].size();
if (m == 1 && n == 1) {
return 0;
}
if (k >= m + n - 3) {
return m + n - 2;
}
k = min(k, m + n - 3);
bool visited[m][n][k + 1];
memset(visited, false, sizeof(visited));
queue<Nagato> q;
q.emplace(0, 0, k);
visited[0][0][k] = true;
for (int step = 1; q.size() > 0; ++step) {
int cnt = q.size();
for (int _ = 0; _ < cnt; ++_) {
Nagato cur = q.front();
q.pop();
for (int i = 0; i < 4; ++i) {
int nx = cur.x + dirs[i][0];
int ny = cur.y + dirs[i][1];
if (nx >= 0 && nx < m && ny >= 0 && ny < n) {
if (grid[nx][ny] == 0 && !visited[nx][ny][cur.rest]) {
if (nx == m - 1 && ny == n - 1) {
return step;
}
q.emplace(nx, ny, cur.rest);
visited[nx][ny][cur.rest] = true;
}
else if (grid[nx][ny] == 1 && cur.rest > 0 && !visited[nx][ny][cur.rest - 1]) {
q.emplace(nx, ny, cur.rest - 1);
visited[nx][ny][cur.rest - 1] = true;
}
}
}
}
}
return -1;
}
};
LeetCode1210 穿过迷宫的最少移动次数
//这道题灵茶山艾府写得非常巧妙,值得直接学习他的写法与思路
//灵茶山艾府题解链接:https://leetcode.cn/problems/minimum-moves-to-reach-target-with-rotations/solutions/2093126/huan-zai-if-elseyi-ge-xun-huan-chu-li-li-tw8b/
综合应用
LeetCode1631 最小体力消耗路径
class Solution {
public:
int minimumEffortPath(vector<vector<int>>& heights) {
int row = heights.size(), column = heights[0].size(), left = 0, right = 1e6, middle;
if (row == 1 && column == 1) return 0;//已经位于终点
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
auto check = [&](int middle) -> bool {
vector<vector<bool>> flag(row, vector<bool>(column));
vector<int> r, c;
r.emplace_back(0), c.emplace_back(0);
flag[0][0] = true;
int front = 0, rear = 1, tail;
while (front < rear) {
tail = rear;
while (front < tail) {
for (int i = 0; i < 4; ++ i) {
int x = dx[i] + r[front], y = dy[i] + c[front];
if (x >= 0 && x < row && y >= 0 && y < column && !flag[x][y] && abs(heights[x][y] - heights[r[front]][c[front]]) <= middle) {
if (x == row - 1 && y == column - 1) return true;
r.emplace_back(x), c.emplace_back(y);
flag[x][y] = true;
}
}
++ front;
}
rear = r.size();
}
return false;
};
while (left < right) {
middle = (left + right) >> 1;
if (check(middle)) {
right = middle;
} else {
left = middle + 1;
}
}
return left;
}
};
LeetCode778 水位上升的游泳池中游泳
class Solution {
private:
int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
public:
int swimInWater(vector<vector<int>>& grid) {
int n = grid.size();
if (n == 1) return grid[0][0];
auto check = [&](int x) -> bool {
int que[n * n];
que[0] = 0;
vector<bool> vis(n * n, false);
vis[0] = true;
int front = 0, rear = 1, tail;
while (front < rear) {
tail = rear;
while (front < tail) {
int r = que[front] / n, c = que[front] % n;
for (int i = 0; i < 4; ++ i) {
int _r = r + dx[i], _c = c + dy[i];
int val = _r * n + _c;
if (_r >= 0 && _r < n && _c >= 0 && _c < n && !vis[val] && grid[_r][_c] <= x) {
if (_r == n - 1 && _c == n - 1) return true;
vis[val] = true;
que[rear ++] = val;
}
}
++ front;
}
}
return false;
};
int left = grid[0][0], right = n * n, mid;
while (left < right) {
mid = left + right >> 1;
if (check(mid)) {
right = mid;
} else {
left = mid + 1;
}
}
return left;
}
};
LeetCode1263 推箱子
//力扣官方题解
class Solution {
public:
int minPushBox(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
int sx, sy, bx, by; // 玩家、箱子的初始位置
for (int x = 0; x < m; x++) {
for (int y = 0; y < n; y++) {
if (grid[x][y] == 'S') {
sx = x;
sy = y;
} else if (grid[x][y] == 'B') {
bx = x;
by = y;
}
}
}
auto ok = [&](int x, int y) -> bool { // 不越界且不在墙上
return x >= 0 && x < m && y >= 0 && y < n && grid[x][y] != '#';
};
vector<int> d = {0, -1, 0, 1, 0};
vector<vector<int>> dp(m * n, vector<int>(m * n, INT_MAX));
queue<pair<int, int>> q;
dp[sx * n + sy][bx * n + by] = 0; // 初始状态的推动次数为 0
q.push({sx * n + sy, bx * n + by});
while (!q.empty()) {
queue<pair<int, int>> q1;
while (!q.empty()) {
auto [s1, b1] = q.front();
q.pop();
int sx1 = s1 / n, sy1 = s1 % n, bx1 = b1 / n, by1 = b1 % n;
if (grid[bx1][by1] == 'T') { // 箱子已被推到目标处
return dp[s1][b1];
}
for (int i = 0; i < 4; i++) { // 玩家向四个方向移动到另一个状态
int sx2 = sx1 + d[i], sy2 = sy1 + d[i + 1], s2 = sx2*n+sy2;
if (!ok(sx2, sy2)) { // 玩家位置不合法
continue;
}
if (bx1 == sx2 && by1 == sy2) { // 推动箱子
int bx2 = bx1 + d[i], by2 = by1 + d[i + 1], b2 = bx2*n+by2;
if (!ok(bx2, by2) || dp[s2][b2] <= dp[s1][b1] + 1) { // 箱子位置不合法 或 状态已访问
continue;
}
dp[s2][b2] = dp[s1][b1] + 1;
q1.push({s2, b2});
} else {
if (dp[s2][b1] <= dp[s1][b1]) { // 状态已访问
continue;
}
dp[s2][b1] = dp[s1][b1];
q.push({s2, b1});
}
}
}
q.swap(q1);
}
return -1;
}
};
LeetCode2258 逃离火灾
class Solution {
/*
先预处理出全部的方格的最早着火时间
处理方案是从初始为1的方格进行多源BFS
每次扩展一格时间就加1
为了区别开时间2和墙2
可以将墙置为-1,那么任何时间都大于墙而无法通过
最后二分枚举可能到达终点的最大停留时间即可
*/
private:
constexpr static int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int m, n;
public:
bool check(vector<vector<int>>& grid, int t) {
if (grid[0][0] && t >= grid[0][0]) return false;
vector<pair<int, int>> que;
que.emplace_back(make_pair(0, 0));
vector<vector<bool>> vis(m, vector<bool>(n, false));
vis[0][0] = true;
int front = 0, rear = 1, tail;
while (front < rear) {
tail = rear;
++ t;
while (front < tail) {
int fir = que[front].first, sec = que[front].second;
++ front;
for (int i = 0; i < 4; ++ i) {
int x = fir + dir[i][0], y = sec + dir[i][1];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y] && (!grid[x][y] || t <= grid[x][y])) {
if (x == m - 1 && y == n - 1) {
return true;
} else if (t != grid[x][y]) {
vis[x][y] = true;
que.emplace_back(make_pair(x, y));
}
}
}
rear = que.size();
}
}
return false;
}
void bfs(vector<vector<int>>& grid) {
vector<pair<int, int>> fire;
for (int i = 0; i < m; ++ i) {
for (int j = 0; j < n; ++ j) {
if (grid[i][j] == 1) {
fire.emplace_back(make_pair(i, j));
} else if (grid[i][j] == 2) {
grid[i][j] = -1;
}
}
}
int front = 0, rear = fire.size(), tail, t = 1;
while (front < rear) {
tail = rear;
while (front < tail) {
int fir = fire[front].first, sec = fire[front].second;
++ front;
for (int i = 0; i < 4; ++ i) {
int x = fir + dir[i][0], y = sec + dir[i][1];
if (x >= 0 && x < m && y >= 0 && y < n && !grid[x][y]) {
grid[x][y] = t;
fire.emplace_back(make_pair(x, y));
}
}
}
++ t;
rear = fire.size();
}
}
int maximumMinutes(vector<vector<int>>& grid) {
m = grid.size(), n = grid[0].size();
bfs(grid);
int left = 0, right = m * n, middle, ans = -1;
while (left <= right) {
middle = left + right >> 1;
if (check(grid, middle)) {
ans = middle;
left = middle + 1;
} else {
right = middle - 1;
}
}
return ans == m * n ? 1e9 : ans;
}
};
LeetCode2577 在网格图中访问一个格子的最少时间
class Solution {
/*
假设初始时,可以从[0, 0]移动到任意相邻点
那么必定是存在走到一个走到右下角的可行方案的
因为这样子必定可以来回走消耗时间
直至可以走向下一个新格子
假设走到(x, y)
那么再次走到(x, y)至少需要走2步(离开,回去)
由此,若从(x0, y0)想走向(x1, y1)
此时需要判断(x1, y1)的值val1与(x0, y0)的值val2的关系
若val1 <= val2,下一步可以直接通行
若val1 > val2,当(val1 - val2) % 2 == 0时,需要(val1 - val2 + 1)步,否则需要(val1 - val2)步
还不能走的点,可以存到优先队列中,由此可以得到最短可以走的时间
最后,按照上述思路进行BFS即可
*/
public:
constexpr static int dir[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int minimumTime(vector<vector<int>>& grid) {
if (grid[0][1] > 1 && grid[1][0] > 1) {
return -1;
}
int m = grid.size(), n = grid[0].size(), t = 0;
vector<vector<bool>> vis(m, vector<bool>(n, false));
vis[0][0] = true;
priority_queue<tuple<int, int, int>, vector<tuple<int, int, int>>, greater<tuple<int, int, int>>> pq;//时间,行,列
pq.push(tuple<int, int, int>(0, 0, 0));
while (pq.empty() == false) {
while (!pq.empty() && t >= get<0>(pq.top())) {
int r = get<1>(pq.top()), c = get<2>(pq.top());
if (r == m - 1 && c == n - 1) {
return t;
}
for (int i = 0; i < 4; ++ i) {
int x = r + dir[i][0];
int y = c + dir[i][1];
if (x >= 0 && x < m && y >= 0 && y < n && !vis[x][y]) {
vis[x][y] = true;
int nt = grid[x][y] <= t ? t + 1 : grid[x][y] + !((grid[x][y] - t) & 1);
pq.push(tuple<int, int, int>(nt, x, y));
}
}
pq.pop();
}
t = get<0>(pq.top());
}
return -1;
}
};
总结
知识总结
- DFS
DFS 是一种递归或回溯的搜索方法,它从起始节点开始,尽可能深入地探索图的分支,直到无法继续或达到目标节点。在探索过程中,它会标记已经访问过的节点,以避免重复访问。DFS 常用于寻找连通分量、拓扑排序、递归问题等。
- BFS
BFS 是一种逐层搜索的方法,它从起始节点开始,逐层遍历相邻节点,直到找到目标节点或遍历完所有节点。BFS 使用队列来存储待访问的节点,按照先进先出的原则进行访问。BFS 常用于寻找最短路径、最小生成树、图的连通性等问题。
- 并查集
并查集是一种树型的数据结构,用于处理一些不相交集合(Disjoint Sets)的合并及查询问题。常常在使用中以森林来表示。
题型总结
- DFS
在网格图中,是否存在一条路径(x1, y1)连通(x2, y2)。
- BFS
在边长相等的图中寻找(x1, y1)与(x2, y2)之间的最短路。
- 图上二分
需要在一个范围内找出图论的最优解。
- 图的连通性
可以抽象理解为能否使得图中不同的点在一个集合内。常用DFS、并查集或者Tarjan算法(处理强连通分量)解决。
模板:网格图DFS
constexpr static int DIR[4][2] = {{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
int row, column;
Return_Type dfs(vector<vector<Element_Type>>& grid, vector<vector<bool>>& visited, int r, int c) {
Return_Type ans;// omit if there is no return value
// begin: core code
visited[r][c] = true;// use grid to set flags if possible
for (int i = 0; i < 4; ++ i) {
int ner = r + DIR[i][0], nec = c + DIR[i][1];
if (ner >= 0 && ner < row && nec >= 0 && nec < column && !visited[ner][nec]) {
Return_Type res = dfs(grid, visited, ner, nec);
// handle the relationship between ans and res
}
}
// end: core code
return ans;// omit if there is no return value
}
模板:网格图BFS
Return_Type bfs(vector<vector<Element_Type>>& grid, int startRow, int startColumn) {
Return_Type ans;// omit if there is no return value
// begin: core code
vector<vector<Element_Type>>& visited;// use grid to set flags if possible
vector<int> r, c;
r.emplace_back(startRow), c.emplace_back(startColumn);
visited[startRow][startColumn] = true;
while (r.size() > 0) {
vector<int> _r, _c;
for (int i = 0, sz = r.size(); i < sz; ++ i) {
for (int j = 0; j < 4; ++ j) {
int ner = r[i] + DIR[j][0], nec = c[i] + DIR[j][1];
if (ner >= 0 && ner < row && nec >= 0 && nec < column && !visited[ner][nec]) {
visited[ner][nec] = true;
Return_Type res = ......;
_r.emplace(ner), _c.emplace_back(nec);
// handle the relationship between ans and res
}
}
}
r = move(_r), c = move(_c);
}
// end: core code
return ans;// omit if there is no return value
}
模板:网格图二分查找
bool check(int mid) {
// execute dfs()/bfs() under the limit of "mid"
// return false when encountering unsatisfactory situations
return true;
}
void solve() {
int le = LEFT, ri = RIGHT, mid;
while (le < ri) {
mid = (le + ri) >> 1;
if (check(mid)) {
} else {
}
}
}
模板:网格图并查集
int bcj[ELEMENT_NUMBER];
void init() {
for (int i = 1; i < ELEMENT_NUMBER; ++ i) {
bcj[i] = i;
}
}
int find(int x) {
if (x != bcj[x]) return bcj[x] = find(bcj[x]);
return x;
}
void merge(int x, int y) {
bcj[find(x)] = find(y);
}
void solve() {
// sort the elements of grid
// use Disjoint-set to handle the elements
}
模板:网格图带权并查集
int bcj[N], sz[N];
bool merge(int x, int y) {
x = find(x);
y = find(y);
if (x == y) return false;
sz[x] += sz[y];
bcj[y] = x;
return true;
}
个人理解
- DFS
DFS 属于“不撞南墙不回头”的方法,通常会一条路走到黑。因此,DFS更擅长处理只需要寻找某一种合法或者不合法的情况的问题。
- BFS
BFS 可以优先访问离起始点较近的节点,因此在寻找最短路径或最小步数等问题上表现较好。在图的连通性上也有不错的表现。
浙公网安备 33010602011771号