力扣dfs

DFS(深度优先搜索)
深度优先搜索也可以用来检测环路:记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,则说明有环。我们也可以用之后会讲到的拓扑排序判断是否有环路,若最后存在入度不为零的点,则说明有环。
有时我们可能会需要对已经搜索过的节点进行标记,以防止在遍历时重复搜索某个节点,这种做法叫做状态记录或记忆化(memoization)。
给定一个二维的 0-1 矩阵,其中 0 表示海洋,1 表示陆地。单独的或相邻的陆地可以形成岛屿,每个格子只与其上下左右四个格子相邻。求最大的岛屿面积。

Input:
[[1,0,1,1,0,1,0,1],
[1,0,1,1,0,1,1,1],
[0,0,0,0,0,0,0,1]]
Output: 6

最大的岛屿面积为 6,位于最右侧。
一般来说,深度优先搜索类型的题可以分为主函数和辅函数,主函数用于遍历所有的搜索位置,判断是否可以开始搜索,如果可以即在辅函数进行搜索。辅函数则负责深度优先搜索的递归调用。当然,我们也可以使用栈
(stack)实现深度优先搜索,但因为栈与递归的调用原理相同,而递归相对便于实现,因此刷题时笔者推荐使用递归式写法,同时也方便进行回溯(见下节)。不过在实际工程上,直接使用栈可能才是最好的选择,一是因为便于理解,二是更不易出现递归栈满的情况。我们先展示使用栈的写法。
map是关联容器的一种,map是由一个个pair组成 。假设有一个寝室501,如图,寝室有4个学生,我们要用一种容器来保存这4个学生的学号和姓名,我们可以用一个pair保存一个学生的学号和姓名,一个map来保存一个寝室的4个学生,map的成员就是4个pair。
比较赋值简便表示
area=max(area,local_area)

vector<int> direction{
    
    -1, 0, 1, 0, -1};
int maxAreaOfIsland(vector<vector<int>>& grid) {
    
    
int m = grid.size(), n = m? grid[0].size(): 0, local_area, area = 0, x, y;
for (int i = 0; i < m; ++i) {
    
    
for (int j = 0; j < n; ++j) {
    
    
if (grid[i][j]) {
    
    
local_area = 1;
grid[i][j] = 0;
stack<pair<int, int>> island;
island.push({
    
    i, j});
while (!island.empty()) {
    
    
auto [r, c] = island.top();
island.pop();
for (int k = 0; k < 4; ++k) {
    
    
x = r + direction[k], y = c + direction[k+1];
if (x >= 0 && x < m &&
y >= 0 && y < n && grid[x][y] == 1) {
    
    
grid[x][y] = 0;
++local_area;
island.push({
    
    x, y});
}
}
}
area = max(area, local_area);
}
}
}
return area;
}

在辅函数里,一个一定要注意的点是辅函数内递归搜索时,边界条件的判定。边界判定一般
有两种写法,一种是先判定是否越界,只有在合法的情况下才进行下一步搜索(即判断放在调用
递归函数前);另一种是不管三七二十一先进行下一步搜索,待下一步搜索开始时再判断是否合
法(即判断放在辅函数第一行)。我们这里分别展示这两种写法。

vector<int> direction{
    
    -1, 0, 1, 0, -1};
// 主函数
int maxAreaOfIsland(vector<vector<int>>& grid) {
    
    
if (grid.empty() || grid[0].empty()) return 0;
int max_area = 0;
for (int i = 0; i < grid.size(); ++i) {
    
    
for (int j = 0; j < grid[0].size(); ++j) {
    
    
if (grid[i][j] == 1) {
    
    
max_area = max(max_area, dfs(grid, i, j));
}
}
}
return max_area;
}
// 辅函数
int dfs(vector<vector<int>>& grid, int r, int c) {
    
    
if (grid[r][c] == 0) return 0;
grid[r][c] = 0;
int x, y, area = 1;
for (int i = 0; i < 4; ++i) {
    
    
x = r + direction[i], y = c + direction[i+1];
if (x >= 0 && x < grid.size() && y >= 0 && y < grid[0].size()) {
    
    
area += dfs(grid, x, y);
}
}
return area;
}
int maxAreaOfIsland(vector<vector<int>>& grid) {
    
    
if (grid.empty() || grid[0].empty()) return 0;
int max_area = 0;
for (int i = 0; i < grid.size(); ++i) {
    
    
for (int j = 0; j < grid[0].size(); ++j) {
    
    
max_area = max(max_area, dfs(grid, i, j));
}
}
return max_area;
}
// 辅函数
int dfs(vector<vector<int>>& grid, int r, int c) {
    
    
if (r < 0 || r >= grid.size() ||
c < 0 || c >= grid[0].size() || grid[r][c] == 0) {
    
    
return 0;
}
grid[r][c] = 0;
return 1 + dfs(grid, r + 1, c) + dfs(grid, r - 1, c) +
dfs(grid, r, c + 1) + dfs(grid, r, c - 1);
}

给定一个二维的 0-1 矩阵,如果第 (i, j) 位置是 1,则表示第 i 个人和第 j 个人是朋友。已知朋友关系是可以传递的,即如果 a 是 b 的朋友,b 是 c 的朋友,那么 a 和 c 也是朋友,换言之这三个人处于同一个朋友圈之内。求一共有多少个朋友圈。

Input:
[[1,1,0],
[1,1,0],
[0,0,1]]
Output: 2

对于题目 695,图的表示方法是,每个位置代表一个节点,每个节点与上下左右四个节点相
邻。而在这一道题里面,每一行(列)表示一个节点,它的每列(行)表示是否存在一个相邻节
点。因此题目 695 拥有 m × n 个节点,每个节点有 4 条边;而本题拥有 n 个节点,每个节点最多
有 n 条边,表示和所有人都是朋友,最少可以有 1 条边,表示自己与自己相连。当清楚了图的表
示方法后,这道题与题目 695 本质上是同一道题:搜索朋友圈(岛屿)的个数(最大面积)。我
们这里采用递归的第一种写法。

// 主函数
int findCircleNum(vector<vector<int>>& friends) {
    
    
int n = friends.size(), count = 0;
vector<bool> visited(n, false);
for (int i = 0; i < n; ++i) {
    
    
if (!visited[i]) {
    
    
dfs(friends, i, visited);
++count;
}
}
return count;
}
// 辅函数
void dfs(vector<vector<int>>& friends, int i, vector<bool>& visited) {
    
    
visited[i] = true;
for (int k = 0; k < friends.size(); ++k) {
    
    
if (friends[i][k] == 1 && !visited[k]) {
    
    
dfs(friends, k, visited);
}
}
}

给定一个二维的非负整数矩阵,每个位置的值表示海拔高度。假设左边和上边是太平洋,右边和下边是大西洋,求从哪些位置向下流水,可以流到太平洋和大西洋。水只能从海拔高的位置流到海拔低或相同的位置。

Input:
太平洋 ~ ~ ~ ~ ~
~ 1 2 2 3 (5) *
~ 3 2 3 (4) (4) *
~ 2 4 (5) 3 1 *
~ (6) (7) 1 4 5 *
~ (5) 1 1 2 4 *
* * * * * 大西洋
Output: [[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]

虽然题目要求的是满足向下流能到达两个大洋的位置,如果我们对所有的位置进行搜索,那么在不剪枝的情况下复杂度会很高。因此我们可以反过来想,从两个大洋开始向上流,这样我们只需要对矩形四条边进行搜索。搜索完成后,只需遍历一遍矩阵,满足条件的位置即为两个大洋向上流都能到达的位置。

vector<int> direction{
    
    -1, 0, 1, 0, -1};
// 主函数
vector<vector<int>> pacificAtlantic(vector<vector<int>>& matrix) {
    
    
if (matrix.empty() || matrix[0].empty()) {
    
    
return {
    
    };
}
vector<vector<int>> ans;
int m = matrix.size(), n = matrix[0].size();
vector<vector<bool>> can_reach_p(m, vector<bool>(n, false));
vector<vector<bool>> can_reach_a(m, vector<bool>(n, false));
for (int i = 0; i < m; ++i) {
    
    //左右两列
dfs(matrix, can_reach_p, i, 0);
dfs(matrix, can_reach_a, i, n - 1);
}
for (int i = 0; i < n; ++i) {
    
    //上下两行
dfs(matrix, can_reach_p, 0, i);
dfs(matrix, can_reach_a, m - 1, i);
}
for (int i = 0; i < m; i++) {
    
    
for (int j = 0; j < n; ++j) {
    
    
if (can_reach_p[i][j] && can_reach_a[i][j]) {
    
    
ans.push_back(vector<int>{
    
    i, j});
}
}
}
return ans;
}
// 辅函数
void dfs(const vector<vector<int>>& matrix, vector<vector<bool>>& can_reach,
int r, int c) {
    
    
if (can_reach[r][c]) {
    
    
return;
}
can_reach[r][c] = true;
int x, y;
for (int i = 0; i < 4; ++i) {
    
    
x = r + direction[i], y = c + direction[i+1];
if (x >= 0 && x < matrix.size()
&& y >= 0 && y < matrix[0].size() &&
matrix[r][c] <= matrix[x][y]) {
    
    
dfs(matrix, can_reach, x, y);
}
}
}

Guess you like

Origin blog.csdn.net/qq_45598881/article/details/121331896
dfs