每日算法刷题Day66:9.8:leetcode 网格图dfs14道题,用时2h30min
二.网格图DFS
1.套路
1.适用于需要计算连通块个数、大小的题目。
网格图递归:
递归入口:网格图的某个格子
递归方向:一般为左右上下的相邻格子
递归边界:出界、遇到障碍或者已访问
2.本质还是连通块思路,只不过要再判断“出界、遇到障碍”
代码模版
class Solution {
public:vector<vector<bool>> vis;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};int n, m;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<char>>& grid) {vis[x][y] = true; // 访问(x,y)// check(x,y) // 检测(x,y)是否符合特殊条件for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] == '1' && !vis[nx][ny])dfs(nx, ny, grid);}}int numIslands(vector<vector<char>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {// 只进行基础判断if (grid[i][j] == '1' && !vis[i][j]) {dfs(i, j, grid);++res;}}}return res;}
};
2.题目描述
3.学习经验
1.题目特殊条件可以让枚举四个方向变为八个或者固定的(记录朝向)
1. 200. 岛屿数量(中等,无回溯,有vis)
200. 岛屿数量 - 力扣(LeetCode)
思想
1.给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
2.本质是计算连通块的数量
代码
class Solution {
public:vector<vector<bool>> vis;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};int n, m;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<char>>& grid) {vis[x][y] = true;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] == '1' && !vis[nx][ny])dfs(nx, ny, grid);}}int numIslands(vector<vector<char>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] == '1' && !vis[i][j]) {dfs(i, j, grid);++res;}}}return res;}
};
2. 695. 岛屿的最大面积(中等,无回溯,有vis)
695. 岛屿的最大面积 - 力扣(LeetCode)
思想
1.给你一个大小为 m x n
的二进制矩阵 grid
。
岛屿 是由一些相邻的 1
(代表土地) 构成的组合,这里的「相邻」要求两个 1
必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid
的四个边缘都被 0
(代表水)包围着。
岛屿的面积是岛上值为 1
的单元格的数目。
计算并返回 grid
中最大的岛屿面积。如果没有岛屿,则返回面积为 0
。
2.本质是求最大连通块的大小
代码
class Solution {
public:int n, m;vector<vector<bool>> vis;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }int dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = true;int size = 1;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] == 1 && !vis[nx][ny])size += dfs(nx, ny, grid);}return size;}int maxAreaOfIsland(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] == 1 && !vis[i][j]) {int size = dfs(i, j, grid);res = max(res, size);}}}return res;}
};
3. 3619. 总价值可以被K整除的岛屿数量(中等,无回溯,有vis)
3619. 总价值可以被 K 整除的岛屿数目 - 力扣(LeetCode)
思想
1.给你一个 m x n
的矩阵 grid
和一个正整数 k
。一个 岛屿 是由 正 整数(表示陆地)组成的,并且陆地间 四周 连通(水平或垂直)。
一个岛屿的总价值是该岛屿中所有单元格的值之和。
返回总价值可以被 k
整除 的岛屿数量。
2.本质还是遍历连通块
代码
class Solution {
public:int n, m;vector<vector<bool>> vis;typedef long long ll;ll cnt;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {cnt += grid[x][y];vis[x][y] = true;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] > 0 && !vis[nx][ny])dfs(nx, ny, grid);}}int countIslands(vector<vector<int>>& grid, int k) {n = grid.size(), m = grid[0].size();int res = 0;vis.assign(n, vector<bool>(m, false));for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] > 0 && !vis[i][j]) {cnt = 0;dfs(i, j, grid);if (cnt % k == 0)++res;}}}return res;}
};
4. 面试题 16.19. 水域大小(中等,无回溯,有vis)
面试题 16.19. 水域大小 - 力扣(LeetCode)
思想
1.你有一个用于表示一片土地的整数矩阵land
,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。
2."对角连接"增加遍历范围,从4个变成8个即可
代码
class Solution {
public:int n, m;vector<vector<bool>> vis;vector<int> dx = {1, -1, 0, 0, 1, 1, -1, -1},dy = {0, 0, 1, -1, 1, -1, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }int dfs(int x, int y, vector<vector<int>>& land) {vis[x][y] = true;int size = 1;for (int i = 0; i < 8; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && land[nx][ny] == 0 && !vis[nx][ny])size += dfs(nx, ny, land);}return size;}vector<int> pondSizes(vector<vector<int>>& land) {n = land.size(), m = land[0].size();vis.assign(n, vector<bool>(m, false));vector<int> res;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (land[i][j] == 0 && !vis[i][j]) {res.push_back(dfs(i, j, land));}}}sort(res.begin(), res.end());return res;}
};
5. LCS 03. 主题空间(中等,无回溯,有vis)
思想
1.「以扣会友」线下活动所在场地由若干主题空间与走廊组成,场地的地图记作由一维字符串型数组 grid
,字符串中仅包含 "0"~"5"
这 6 个字符。地图上每一个字符代表面积为 1 的区域,其中 "0"
表示走廊,其他字符表示主题空间。相同且连续(连续指上、下、左、右四个方向连接)的字符组成同一个主题空间。
假如整个 grid
区域的外侧均为走廊。请问,不与走廊直接相邻的主题空间的最大面积是多少?如果不存在这样的空间请返回 0
。
2.本质上是遍历每个连通块,在遍历的时候判断”是否与走廊直接相邻“,然后标记完一个主题空间后根据判断结果更新答案
代码
class Solution {
public:vector<vector<bool>> vis;int n, m;bool tag;int cnt;char start;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<string>& grid) {vis[x][y] = true;++cnt;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny) || grid[nx][ny] == '0') {tag = false;continue;}if (!vis[nx][ny] && grid[nx][ny] == start)dfs(nx, ny, grid);}}int largestArea(vector<string>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] > '0' && !vis[i][j]) {tag = true;cnt = 0;start = grid[i][j];dfs(i, j, grid);if (tag)res = max(res, cnt);}}}return res;}
};
6. 463. 岛屿的周长(简单,无回溯,有vis)
463. 岛屿的周长 - 力扣(LeetCode)
思想
1.给定一个 row x col
的二维网格地图 grid
,其中:grid[i][j] = 1
表示陆地, grid[i][j] = 0
表示水域。
网格中的格子 水平和垂直 方向相连(对角线方向不相连)。整个网格被水完全包围,但其中恰好有一个岛屿(或者说,一个或多个表示陆地的格子相连组成的岛屿)。
岛屿中没有“湖”(“湖” 指水域在岛屿内部且不和岛屿周围的水相连)。格子是边长为 1 的正方形。网格为长方形,且宽度和高度均不超过 100 。计算这个岛屿的周长。
2.本质是求一个连通块的数量,以及边的数量(两个邻居相邻的边可以理解为图两个节点的边)
代码
class Solution {
public:int n, m;vector<vector<bool>> vis;int size;int dif;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = true;++size;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] == 1 ){++dif; // 当前节点(x,y)减去一条边if(!vis[nx][ny]) dfs(nx, ny, grid);}}}int islandPerimeter(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();int res = 0;vis.assign(n, vector<bool>(m, false));size=0,dif=0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] == 1 && !vis[i][j]) {dfs(i, j, grid);res = 4 * size - dif;}}}return res;}
};
7. 2658. 网格图中鱼的最大数目(中等,无回溯,有vis)
2658. 网格图中鱼的最大数目 - 力扣(LeetCode)
思想
1.给你一个下标从 0 开始大小为 m x n
的二维整数数组 grid
,其中下标在 (r, c)
处的整数表示:
- 如果
grid[r][c] = 0
,那么它是一块 陆地 。 - 如果
grid[r][c] > 0
,那么它是一块 水域 ,且包含grid[r][c]
条鱼。
一位渔夫可以从任意 水域 格子(r, c)
出发,然后执行以下操作任意次: - 捕捞格子
(r, c)
处所有的鱼,或者 - 移动到相邻的 水域 格子。
请你返回渔夫最优策略下, 最多 可以捕捞多少条鱼。如果没有水域格子,请你返回0
。
格子(r, c)
相邻 的格子为(r, c + 1)
,(r, c - 1)
,(r + 1, c)
和(r - 1, c)
,前提是相邻格子在网格图内。
2.本质是求连通块的数字和的最大值
代码
class Solution {
public:vector<vector<bool>> vis;int n, m;int size;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = true;size += grid[x][y];for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid[nx][ny] > 0 && !vis[nx][ny])dfs(nx, ny, grid);}}int findMaxFish(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] > 0 && !vis[i][j]) {size = 0;dfs(i, j, grid);res = max(res, size);}}}return res;}
};
8. 1034. 边界着色(中等,无回溯,有vis)
1034. 边界着色 - 力扣(LeetCode)
思想
1.给你一个大小为 m x n
的整数矩阵 grid
,表示一个网格。另给你三个整数 row
、col
和 color
。网格中的每个值表示该位置处的网格块的颜色。
如果两个方块在任意 4 个方向上相邻,则称它们 相邻 。
如果两个方块具有相同的颜色且相邻,它们则属于同一个 连通分量 。
连通分量的边界 是指连通分量中满足下述条件之一的所有网格块:
- 在上、下、左、右任意一个方向上与不属于同一连通分量的网格块相邻
- 在网格的边界上(第一行/列或最后一行/列)
请你使用指定颜色color
为所有包含网格块grid[row][col]
的 连通分量的边界 进行着色。
并返回最终的网格grid
。
2.因为题目要求修改原grid
的颜色,而一旦边搜索边修改会影响其他点的判断,所以依旧采取dfs标记,标记完后统一修改。这里的优化是标记完确定要着色,将待着色的点放入数组中,dfs完后直接遍历着色数组即可
代码
class Solution {
public:int n, m;vector<vector<int>> vis;int start;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = 1;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny) || grid[nx][ny] != start) {vis[x][y] = 2;continue;}if (!vis[nx][ny])dfs(nx, ny, grid);}}vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col,int color) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<int>(m, 0));start = grid[row][col];dfs(row, col, grid);for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (vis[i][j] == 2)grid[i][j] = color;}}return grid;}
};
优化,在dfs里面存储着色点对数组,dfs遍历完后遍历数组,而不再两层遍历
class Solution {
public:int n, m;vector<vector<int>> vis;int start;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};typedef pair<int, int> PII;vector<PII> change;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = 1;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny) || grid[nx][ny] != start) {vis[x][y] = 2;continue;}if (!vis[nx][ny])dfs(nx, ny, grid);}if (vis[x][y] == 2)change.push_back({x, y});}vector<vector<int>> colorBorder(vector<vector<int>>& grid, int row, int col,int color) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<int>(m, 0));start = grid[row][col];dfs(row, col, grid);for (auto& tmp : change) {grid[tmp.first][tmp.second] = color;}return grid;}
};
9. 1020. 飞地的数量(中等,无回溯,有vis)
1020. 飞地的数量 - 力扣(LeetCode)
思想
1.给你一个大小为 m x n
的二进制矩阵 grid
,其中 0
表示一个海洋单元格、1
表示一个陆地单元格。
一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid
的边界。
返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。
2.本质是在遍历标记连通块的过程中判断这个连通块靠不靠近边界
代码
class Solution {
public:vector<vector<bool>> vis;int n, m;bool tag;int size;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = true;++size;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny)) {tag = false;continue;}if (grid[nx][ny] == 1 && !vis[nx][ny])dfs(nx, ny, grid);}}int numEnclaves(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] == 1 && !vis[i][j]) {tag = true;size = 0;dfs(i, j, grid);if (tag)res += size;}}}return res;}
};
10. 2684. 矩阵中移动的最大次数(中等,无回溯,无vis)
2684. 矩阵中移动的最大次数 - 力扣(LeetCode)
思想
1.给你一个下标从 0 开始、大小为 m x n
的矩阵 grid
,矩阵由若干 正 整数组成。
你可以从矩阵第一列中的 任一 单元格出发,按以下方式遍历 grid
:
- 从单元格
(row, col)
可以移动到(row - 1, col + 1)
、(row, col + 1)
和(row + 1, col + 1)
三个单元格中任一满足值 严格 大于当前单元格的单元格。
返回你在矩阵中能够 移动 的 最大 次数。
2.题目是要找所有路径的最大次数,是递归返回路径的次数,无需知道路径细节,所以无需回溯。因为移动只向右侧移动,所以不会死循环,无需vis数组。
但是一个点会覆盖多条路径,会算多次,所以可以拿个数组记录从它开始的最大移动次数,只需dfs计算一次,进行记忆化搜索。
代码
class Solution {
public:vector<vector<int>> cnts;int n, m;int dfs(int x, int y, vector<vector<int>>& grid) {if (cnts[x][y] > 0)return cnts[x][y];if (y == m - 1)return 0;int cnt = 0;if (x - 1 >= 0 && grid[x - 1][y + 1] > grid[x][y])cnt = max(cnt, dfs(x - 1, y + 1, grid) + 1);if (grid[x][y + 1] > grid[x][y])cnt = max(cnt, dfs(x, y + 1, grid) + 1);if (x + 1 < n && grid[x + 1][y + 1] > grid[x][y])cnt = max(cnt, dfs(x + 1, y + 1, grid) + 1);cnts[x][y] = cnt;return cnt;}int maxMoves(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();cnts.assign(n, vector<int>(m, 0));int res = 0;for (int i = 0; i < n; ++i) {res = max(res, dfs(i, 0, grid));}return res;}
};
11. 1254. 统计封闭岛屿的数目(中等)
1254. 统计封闭岛屿的数目 - 力扣(LeetCode)
思想
1.二维矩阵 grid
由 0
(土地)和 1
(水)组成。岛是由最大的4个方向连通的 0
组成的群,封闭岛是一个 完全
由1包围(左、上、右、下)的岛。
请返回 封闭岛屿 的数目。
2.本质是判断连通块是否与网格图边界相连
代码
class Solution {
public:vector<vector<bool>> vis;int n, m;bool tag;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid) {vis[x][y] = true;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny)) {tag = false;continue;}if (grid[nx][ny] == 0 && !vis[nx][ny])dfs(nx, ny, grid);}}int closedIsland(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid[i][j] == 0 && !vis[i][j]) {tag = true;dfs(i, j, grid);if (tag)++res;}}}return res;}
};
12. 130. 被围绕的区域(中等,学习,无回溯,有vis)
130. 被围绕的区域 - 力扣(LeetCode)
思想
1.给你一个 m x n
的矩阵 board
,由若干字符 'X'
和 'O'
组成,捕获 所有 被围绕的区域:
- **连接:**一个单元格与水平或垂直方向上相邻的单元格连接。
- 区域:连接所有
'O'
的单元格来形成一个区域。 - 围绕:如果您可以用
'X'
单元格 连接这个区域,并且区域中没有任何单元格位于board
边缘,则该区域被'X'
单元格围绕。
通过 原地 将输入矩阵中的所有'O'
替换为'X'
来 捕获被围绕的区域。你不需要返回任何值。
2.这题本质也是判断一个连通块是否接触边界,然后所有连通块遍历完了将不接触边界的一整块替换,所以这里面不仅要判断这个节点是否被遍历,还要判断它属于哪块连通块,所以vis
数组要开int
的,只有-1
表示未遍历,利用一个自增id
来记录是几号连通块,再利用一个判断数组判断这块连通块是否要替换。
3.优化方法是从边界上的O
出发,标记访问过的O
,所以最终访问过的O
不用替换,未访问的不用替换,将O
分为属于边界连通块的和不属于边界连通块的,无需判断它属于具体哪一块,更简单
代码
正常遍历:
class Solution {
public:vector<vector<int>> vis;vector<bool> change;int n, m;bool tag;int id;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<char>>& board) {vis[x][y] = id;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (!inMap(nx, ny)) {tag = false;continue;}if (board[nx][ny] == 'O' && vis[nx][ny] == -1)dfs(nx, ny, board);}}void solve(vector<vector<char>>& board) {n = board.size(), m = board[0].size();vis.assign(n, vector<int>(m, -1));id = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (board[i][j] == 'O' && vis[i][j] == -1) {tag = true;dfs(i, j, board);change.push_back(tag);++id;}}}for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (board[i][j] == 'O' && change[vis[i][j]]) {board[i][j] = 'X';}}}}
};
边界出发优化:
class Solution {
public:vector<vector<bool>> vis;int n, m;bool tag;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<char>>& board) {vis[x][y] = true;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && board[nx][ny] == 'O' && !vis[nx][ny])dfs(nx, ny, board);}}void solve(vector<vector<char>>& board) {n = board.size(), m = board[0].size();vis.assign(n, vector<bool>(m, false));// 边界访问for(int i=0;i<n;++i){if(board[i][0]=='O' && !vis[i][0]) dfs(i,0,board);if(board[i][m-1]=='O' && !vis[i][m-1]) dfs(i,m-1,board);}for(int j=1;j<m-1;++j){if(board[0][j]=='O' && !vis[0][j]) dfs(0,j,board);if(board[n-1][j]=='O' && !vis[n-1][j]) dfs(n-1,j,board);}for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (board[i][j] == 'O' && !vis[i][j]) {board[i][j] = 'X';}}}}
};
13. 1905. 统计子岛屿(中等,无回溯,有vis)
1905. 统计子岛屿 - 力扣(LeetCode)
思想
1.给你两个 m x n
的二进制矩阵 grid1
和 grid2
,它们只包含 0
(表示水域)和 1
(表示陆地)。一个 岛屿 是由 四个方向 (水平或者竖直)上相邻的 1
组成的区域。任何矩阵以外的区域都视为水域。
如果 grid2
的一个岛屿,被 grid1
的一个岛屿 完全 包含,也就是说 grid2
中该岛屿的每一个格子都被 grid1
中同一个岛屿完全包含,那么我们称 grid2
中的这个岛屿为 子岛屿 。
请你返回 grid2
中 子岛屿 的 数目 。
2.我的想法是先dfs grid1
标记岛屿,然后dfs grid2
标记岛屿,且不是grid1
的岛屿则全局变量变成false
,但这样麻烦了,不是grid1
的岛屿即水域,在grid1
中天然就是0,即grid2
中当前岛屿一个位置在grid1
中为0,则它必定不是子岛屿,反之则是,一次dfs grid2
即可
代码
class Solution {
public:int n, m;vector<vector<bool>> vis1, vis2;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool tag;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs1(int x, int y, vector<vector<int>>& grid1) {vis1[x][y] = true;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid1[nx][ny] == 1 && !vis1[nx][ny])dfs1(nx, ny, grid1);}}void dfs2(int x, int y, vector<vector<int>>& grid2) {vis2[x][y] = true;if (!vis1[x][y])tag = false;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid2[nx][ny] == 1 && !vis2[nx][ny])dfs2(nx, ny, grid2);}}int countSubIslands(vector<vector<int>>& grid1,vector<vector<int>>& grid2) {n = grid1.size(), m = grid1[0].size();vis1.assign(n, vector<bool>(m, false));vis2.assign(n, vector<bool>(m, false));for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid1[i][j] == 1 && !vis1[i][j])dfs1(i, j, grid1);}}int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid2[i][j] == 1 && !vis2[i][j]) {tag = true;dfs2(i, j, grid2);if (tag)++res;}}}return res;}
};
优化一次dfs:
class Solution {
public:int n, m;vector<vector<bool>> vis;vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};bool tag;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }void dfs(int x, int y, vector<vector<int>>& grid2,vector<vector<int>>& grid1) {vis[x][y] = true;if (grid1[x][y] == 0)tag = false;for (int i = 0; i < 4; ++i) {int nx = x + dx[i], ny = y + dy[i];if (inMap(nx, ny) && grid2[nx][ny] == 1 && !vis[nx][ny])dfs(nx, ny, grid2, grid1);}}int countSubIslands(vector<vector<int>>& grid1,vector<vector<int>>& grid2) {n = grid1.size(), m = grid1[0].size();vis.assign(n, vector<bool>(m, false));int res = 0;for (int i = 0; i < n; ++i) {for (int j = 0; j < m; ++j) {if (grid2[i][j] == 1 && !vis[i][j]) {tag = true;dfs(i, j, grid2, grid1);if (tag)++res;}}}return res;}
};
14. 1391. 检查网格中是否存在有效路径(中等,学习模拟,无回溯,有vis)
1391. 检查网格中是否存在有效路径 - 力扣(LeetCode)
思想
1.给你一个 m x n 的网格 grid
。网格里的每个单元都代表一条街道。grid[i][j]
的街道可以是:
- 1 表示连接左单元格和右单元格的街道。
- 2 表示连接上单元格和下单元格的街道。
- 3 表示连接左单元格和下单元格的街道。
- 4 表示连接右单元格和下单元格的街道。
- 5 表示连接左单元格和上单元格的街道。
- 6 表示连接右单元格和上单元格的街道。
2.跟传统的网格图不同,传统的网格图在当前位置搜索下一个位置四个方向都可以,而此题从某个朝向进入当前位置,要么不能走这个街道,要么会有一个新朝向,这个新朝向保证了下一个位置唯一,所以逻辑为: - (1)枚举四个初始朝向(因为初始街道有的两个朝向都行),开始dfs
- (2)进入当前街道,标记访问,进行判断
- (2.1)当前朝向不能走这条街道,返回false
- (2.2)当前朝向能走这条街道:
- (2.2.1)为最终位置,返回true
- (2.2.2)不是最终位置,得到走完当前街道的新朝向,得到新位置,判断在地图且未访问过,则继续dfs
因为是判断是否存在有效路径,所以dfs返回bool且在探索下一个位置时作为if中的一个条件使用,跟[[十.图论算法-基础遍历#2. 1971. 寻找图中是否存在路径(简单,重点学习,查询一次路径是否存在,无回溯,有vis)]]一样
代码
class Solution {
public:// 朝向:0:下,1:上,2:右,3:左vector<int> dx = {1, -1, 0, 0}, dy = {0, 0, 1, -1};// 街道编号,朝向->新朝向,-1不行vector<vector<int>> vec = {{-1, -1, -1, -1}, {-1, -1, 2, 3}, {0, 1, -1, -1}, {-1, 3, 0, 01},{-1, 2, -1, 0}, {3, -1, 1, -1}, {2, -1, -1, 1},};int n, m;vector<vector<bool>> vis;bool inMap(int x, int y) { return 0 <= x && x < n && 0 <= y && y < m; }bool dfs(int x, int y, vector<vector<int>>& grid, int chao) {vis[x][y] = true;int idx = grid[x][y];int newChao = vec[idx][chao];// 先判断当前访问位置if (newChao == -1)return false;if (x == n - 1 && y == m - 1)return true;int nx = x + dx[newChao], ny = y + dy[newChao];if (inMap(nx, ny) && !vis[nx][ny] && dfs(nx, ny, grid, newChao))return true;return false;}bool hasValidPath(vector<vector<int>>& grid) {n = grid.size(), m = grid[0].size();vis.assign(n, vector<bool>(m, false));bool tag = false;for (int i = 0; i < 4; ++i) {if (dfs(0, 0, grid, i)) {tag = true;break;}}return tag;}
};