携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第6天,点击查看活动详情
所谓深度优先搜索,是我们在遍历到一个新节点时,立即对其相邻的某一个结点进行深度遍历。在处理“树”、“图”这种数据结构的时候,我们往往会采用深度优先搜索算法;当然也可以采用广度优先搜索算法。
深度优先搜索算法通常采用递归的形式,也可以使用栈来模拟这个过程。
考虑如下一个二叉树:
1
/ \
2 3
/
4
如果我们对其进行深度遍历,遍历顺序为根结点->左结点->右节点(即树的先序遍历),那么遍历过程为:1->2->4->3
深度优先搜索可以检测一个“图”中是否存在“环路”,具体表现为:记录遍历过的父级结点,当我们在遍历过程中,遍历到一个已经搜索过的父级结点时,表明存在环路。
下面挑选 4 道题目,主要是二维数组的“图”形式,练习深度优先搜索算法。
695. 岛屿的最大面积 - 简单
给你一个大小为 m x n 的二进制矩阵 grid 。
岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在 水平或者竖直的四个方向上 相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。
岛屿的面积是岛上值为 1 的单元格的数目。
计算并返回 grid 中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
示例:
输入:grid =
[[0,0,1,0,0,0,0,1,0,0,0,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,1,1,0,1,0,0,0,0,0,0,0,0],
[0,1,0,0,1,1,0,0,1,0,1,0,0],
[0,1,0,0,1,1,0,0,1,1,1,0,0],
[0,0,0,0,0,0,0,0,0,0,1,0,0],
[0,0,0,0,0,0,0,1,1,1,0,0,0],
[0,0,0,0,0,0,0,1,1,0,0,0,0]]
输出:6
解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。
题解:
本题可以用深度优先搜索解决,是一道很典型的题目。
首先,我们遍历“地图”——二维数组,如果遇到岛屿——即“1”,那么对其进行深度遍历,同时标记为“0”,表示该点已经搜索过。或者标记为“2”,一遍搜索结束后进行复原。
该点深度遍历后,我们得到该岛屿的面积,与最大面积比较更新。
深度遍历的具体过程如下:我们对该点的上下左右四个方向一次进行深度遍历
- 该点的面积
area初始为 1- 如果某个方向的点在地图内,并且其值为“1”,尚未搜索过,对其进行深度搜索,得到这个方向上的面积,累加到面积
area上- 返回面积
代码:
class Solution {
// 确定搜索方向的辅助数组
private int[] direction = {-1, 0, 1, 0, -1};
public int maxAreaOfIsland(int[][] grid) {
int row = grid.length;
int col = grid[0].length;
int max = 0;
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
// 该点尚未搜索过
if (grid[i][j] == 1) {
// 标记为已搜索
grid[i][j] = 0;
// 深度遍历得到面积
int area = dfs(grid, i, j);
max = Math.max(area, max);
}
}
}
return max;
}
private int dfs(int[][] grid, int x, int y) {
int row = grid.length;
int col = grid[0].length;
// 该点的初始面积为 1
int area = 1;
for (int i = 0; i < 4; i++) {
// 确定搜索方向
int newX = x+direction[i];
int newY = y+direction[i+1];
if (newX >= 0 && newX < row && newY >= 0 && newY < col && grid[newX][newY] == 1) {
grid[newX][newY] = 0;
// 累加该方向上的面积
area += dfs(grid, newX, newY);
}
}
return area;
}
}
200. 岛屿数量 - 简单
给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"] ]
输出:1
题解:
遍历地图(即二维数组
grid)。如果遍历到单元格的值为
'1',那么对当前单元格进行深度优先搜索:
- 将当前单元格置为
'0'- 对当前单元格的上下左右四个方向进行深度优先搜索
题解:
class Solution {
int[] direction = {-1, 0, 1, 0, -1};
public int numIslands(char[][] grid) {
int m = grid.length, n = grid[0].length;
int count = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
count++;
}
}
}
return count;
}
private void dfs(char[][] grid, int x, int y) {
int m = grid.length, n = grid[0].length;
grid[x][y] = '0';
for (int i = 0; i < 4; i++) {
int newX = x+direction[i], newY = y+direction[i+1];
if (newX >= 0 && newX < m && newY >= 0 && newY < n && grid[newX][newY] == '1') {
dfs(grid, newX, newY);
}
}
}
}
417. 太平洋大西洋水流问题 - 中等
有一个 m × n 的矩形岛屿,与 太平洋 和 大西洋 相邻。 “太平洋” 处于大陆的左边界和上边界,而 “大西洋” 处于大陆的右边界和下边界。
这个岛被分割成一个由若干方形单元格组成的网格。给定一个 m x n 的整数矩阵 heights , heights[r][c] 表示坐标 (r, c) 上单元格 高于海平面的高度 。
岛上雨水较多,如果相邻单元格的高度 小于或等于 当前单元格的高度,雨水可以直接向北、南、东、西流向相邻单元格。水可以从海洋附近的任何单元格流入海洋。
返回网格坐标 result 的 2D 列表 ,其中 result[i] = [ri, ci] 表示雨水从单元格 (ri, ci) 流动 既可流向太平洋也可流向大西洋 。
示例:
输入: heights =
[[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]]
输出: [[0,4],[1,3],[1,4],[2,2],[3,0],[3,1],[4,0]]
题解:
题目要求我们找到水既可以流到太平洋也可以流到大西洋的单元格。
我们逆转下思路,从海边开始寻找,某个海洋的水可以从哪个单元格流过来。
- 太平洋的水(即表格的左边界和上边界)来自哪些单元格
- 大西洋的水(即表格的右边界和下边界)来自那些单元格
最后,如果某个单元格都可以流向两个海洋,那么将其坐标加入到结果集中。
我们如何确定某个海洋的水可以从哪个单元格流过来呢?我们可以采取深度优先遍历。
声明两个二维数组
boolean[][]分别表示某个单元格能否流向太平洋和大西洋:
canReachP[i][j],表示单元格[i][j]的水可以流向太平洋canReachA[i][j],表示单元格[i][j]的水可以流向大西洋深度遍历过程如下:
- 从海边开始遍历,即从表格边界开始遍历
- 如果
canReachX[i][j]为true,表示已经搜索过,不必再搜索;否则- 将
canReach[i][j]标记为true,对该单元格的上下左右四个方向进行深度遍历,条件是相邻单元格的高度 >= 当前单元格
代码:
class Solution {
// 方向遍历辅助数组
private int[] direction = {-1,0,1,0,-1};
public List<List<Integer>> pacificAtlantic(int[][] heights) {
int row = heights.length;
int col = heights[0].length;
// 标记数组,某单元格是否可以流向某个海洋
boolean[][] canReachP = new boolean[row][col];
boolean[][] canReachA = new boolean[row][col];
// 从左边界开始搜索可以流向太平洋的单元格,从右边界搜索可以流向大西洋的表格
for (int i = 0; i < row; i++) {
dfs(heights, i, 0, canReachP);
dfs(heights, i, col-1, canReachA);
}
// 从上边界开始搜索可以流向太平洋的单元格,从下边界搜索可以流向大西洋的表格
for (int i = 0; i < col; i++) {
dfs(heights, 0, i, canReachP);
dfs(heights, row-1, i, canReachA);
}
// 结果集
List<List<Integer>> res = new ArrayList<>();
// 确定可以同时流向两个海洋的单元格
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
if (canReachP[i][j] && canReachA[i][j]) {
ArrayList<Integer> temp = new ArrayList<>();
temp.add(i);
temp.add(j);
res.add(temp);
}
}
}
return res;
}
private void dfs(int[][] heights, int x, int y, boolean[][] canReach) {
if (canReach[x][y]) {
return;
}
canReach[x][y] = true;
int row = heights.length;
int col = heights[0].length;
for (int i = 0; i < 4; i++) {
int newX = x+direction[i], newY = y+direction[i+1];
if (newX >= 0 && newX < row && newY >= 0 && newY < col && heights[x][y] <= heights[newX][newY]) {
dfs(heights, newX, newY, canReach);
}
}
}
}
547. 省份数量 - 中等
有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。
省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。
给你一个 n x n 的矩阵 isConnected ,其中 isConnected[i][j] = 1 表示第 i 个城市和第 j 个城市直接相连,而 isConnected[i][j] = 0 表示二者不直接相连。
返回矩阵中 省份 的数量。
示例:
输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2
题解:
我们可以声明一个标记数组
visited,标记某个城市是否已经搜索过。遍历城市,如果某个城市未搜索过,那么对其进行深度遍历:
- 将该城市标记为已经搜索过;
- 根据
isConnected,如果该城市与其他城市X相连,并且X城市尚未搜索过,对其进行深度遍历对某个城市进行深度搜索后,省份数量加一。
最后搜索结束,返回省份数量。
代码:
class Solution {
public int findCircleNum(int[][] isConnected) {
int n = isConnected.length;
boolean[] visited = new boolean[n];
int count = 0;
for (int i = 0; i < n; i++) {
if (!visited[i]) {
dfs(isConnected, visited, n, i);
count++;
}
}
return count;
}
private void dfs(int[][] isConnected, boolean[] visited, int cities, int i) {
visited[i] = true;
for (int j = 0; j < cities; j++) {
if (!visited[j] && isConnected[i][j] == 1) {
dfs(isConnected, visited, cities, j);
}
}
}
}