DFS:深度优先遍历
- 广度优先遍历是一层一层遍历,在遍历过程中遇到新的节点就存储起来,等当前层遍历完之后再遍历下一层。
- 深度优先遍历是遍历过程中遇到新节点就遍历新节点,直到没有新节点就返回到根节点继续遍历。需要用栈存储当前节点信息,对已经遍历过的节点需要做标记。
- 常用来解决“可达性”问题
695. 岛屿的最大面积(Medium)
给定一个包含了一些 0 和 1的非空二维数组 grid , 一个岛屿是由四个方向 (水平或垂直) 的 1 (代表土地) 构成的组合。你可以假设二维矩阵的四个边缘都被水包围着。
找到给定的二维数组中最大的岛屿面积。(如果没有岛屿,则返回面积为0。)
示例 1:
[[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’。
示例 2:
[[0,0,0,0,0,0,0,0]]
对于上面这个给定的矩阵, 返回 0。
注意: 给定的矩阵grid 的长度和宽度都不超过 50。
题解:寻找岛屿的最大面积,能够想到的就是dfs,遍历数组,遇到为1的面积加1,然后当前岛屿值置为0,继续往四个方向dfs,最后返回的就是最大岛屿面积。
class Solution {
//定义四个方向
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
private int m;
private int n;
public int maxAreaOfIsland(int[][] grid) {
if (grid == null || grid.length == 0){
return 0;
}
int maxArea = 0;
m = grid.length;
n = grid[0].length;
//遍历岛屿
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
maxArea = Math.max(maxArea, dfs(grid, i, j));
}
}
return maxArea;
}
//dfs
private int dfs(int[][] grid, int r, int t){
if (r < 0 || r >= m || t < 0 || t >= n || grid[r][t] == 0){
return 0;
}
//遍历到的岛屿置为0
grid[r][t] = 0;
int area = 1;
//往四个方向dfs
for (int[] d : direction) {
area += dfs(grid, r + d[0], t + d[1]);
}
return area;
}
}
200. 岛屿数量(Medium)
给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例 1:
输入:
11110
11010
11000
00000
输出: 1
示例 2:
输入:
11000
11000
00100
00011
输出: 3
题解:与上道题类似,不同的是这道题求的是岛屿的数量,上道题求的是岛屿最大面积。也可以用dfs,遍历数组,遇到值为1的就岛屿数量加1,接着值置为0,然后以这个点为出发,往四个方向进行dfs,遇到连通的岛屿值置为0,直到不能dfs为止。当数组遍历完之后,岛屿数量也就出来了。
class Solution {
//定义四个方向
private int m, n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0) {
return 0;
}
m = grid.length;
n = grid[0].length;
int islandsNum = 0;
//遍历二维数组
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
//遇到值为1的就岛屿数量加一,然后往四周dfs
if (grid[i][j] != '0') {
dfs(grid, i, j);
islandsNum++;
}
}
}
return islandsNum;
}
//dfs
private void dfs(char[][] grid, int i, int j) {
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] == '0') {
return;
}
//将当前值置为0
grid[i][j] = '0';
for (int[] d : direction) {
dfs(grid, i + d[0], j + d[1]);
}
}
}
547. 朋友圈(Medium)
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
示例 1:
输入:
[[1,1,0],
[1,1,0],
[0,0,1]]
输出: 2
说明:已知学生0和学生1互为朋友,他们在一个朋友圈。
第2个学生自己在一个朋友圈。所以返回2。
示例 2:
输入:
[[1,1,0],
[1,1,1],
[0,1,1]]
输出: 1
说明:已知学生0和学生1互为朋友,学生1和学生2互为朋友,所以学生0和学生2也是朋友,所以他们三个在一个朋友圈,返回1。
注意:
N 在[1,200]的范围内。
对于所有学生,有M[i][i] = 1。
如果有M[i][j] = 1,则有M[j][i] = 1。
题解:
- 首先分析题意:一共有n个学生,组成n*n的矩阵M,M[i][j]=1表示i和j是朋友,求出朋友圈的数量。观察矩阵,M[i][j]数字表示第i个或第j个人,在第一行M[0][j],如果值为1,表示第0个人与这些人是朋友,在列中也相同。
- 所以可以准备一个数组标记是否访问过,遍历矩阵第一行,遇到遍历过的直接跳过,没有遍历过的用dfs。
class Solution {
//人数
private int n;
public int findCircleNum(int[][] M) {
n = M.length;
int circleNum = 0;
boolean[] visited = new boolean[n];
for (int i = 0; i < n; i++) {
//没有遍历过的就进行dfs
if (!visited[i]){
dfs(M, i, visited);
circleNum++;
}
}
return circleNum;
}
//dfs
private void dfs(int[][] M, int i, boolean[] visited) {
//遍历过的标记为true
visited[i] = true;
//在同一列进行dfs,同一列值为1的表示是朋友关系,然后在从值为1且没访问过的点出发进行dfs
for (int k = 0; k < n; k++) {
if (M[i][k] == 1 && !visited[k]){
dfs(M, k, visited);
}
}
}
}
130. 被围绕的区域(Medium)
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。
找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:
X X X X
X X X X
X X X X
X O X X
解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
题解:
- 一开始想到的方法是从矩阵内部进行dfs,遇到的每个O都变成X,dfs如果最后O在外围,那么就倒退回去,但是这个方法很麻烦。
- 从他人的解答之后才知道可以先遍历外圈进行dfs,遍历到的O变成¥,这样剩下的就是被包围的O了,这时候将剩下的O变成X,然后将$变成O就恢复了。
- 这种解法的思想是先将不合条件的排除掉,剩下的就是答案了,也就是排除法。
class Solution {
public void solve(char[][] board) {
//遍历外圈
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
//如果外圈中发现'O',进行dfs,将遇到的O变成$
if ((i == 0 || i == board.length - 1 || j == 0 || j == board[0].length - 1) && board[i][j] == 'O'){
solveDFS(board, i, j);
}
}
}
//将被包围的'O'-->'X','$'-->'O'
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[0].length; j++) {
if (board[i][j] == 'O'){
board[i][j] = 'X';
}
if (board[i][j] == '$'){
board[i][j] = 'O';
}
}
}
}
private void solveDFS(char[][] board, int i, int j) {
//遇到的'O'-->'$'
if (board[i][j] == 'O'){
board[i][j] = '$';
//往四个方向进行dfs
//左
if (i > 0 && board[i - 1][j] == 'O'){
solveDFS(board, i - 1, j);
}
//右
if (i < board.length - 1 && board[i + 1][j] == 'O'){
solveDFS(board, i + 1, j);
}
//上
if (j > 0 && board[i][j - 1] == 'O'){
solveDFS(board, i, j - 1);
}
//下
if (j < board[0].length - 1 && board[i][j + 1] == 'O'){
solveDFS(board, i, j + 1);
}
}
}
}
417. 太平洋大西洋水流问题(Medium)
给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。
提示:
输出坐标的顺序不重要 m 和 n 都小于150
示例:
给定下面的 5x5 矩阵:
太平洋 ~ ~ ~ ~ ~
~ 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]] (上图中带括号的单元).
题解:
- 题目让我们找的是哪些点的水流能够流到太平洋和大西洋,一开始想到的办法是遍历所有的点进行dfs,判断哪些点是能够流到四条边的两 边(太平洋和大西洋都要有),但是这样的方法比较复杂,看了其他博主的解答之后才知道此题跟上一题很类似。
- 遍历四条边的点进行dfs,找到哪些点是能够到达的,然后取太平洋和大西洋的交际就是答案了。
class Solution {
private int m;
private int n;
private int[][] direction = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
private int[][] matrix;
public List<List<Integer>> pacificAtlantic(int[][] matrix) {
List<List<Integer>> result = new ArrayList<>();
if (matrix == null || matrix.length == 0){
return result;
}
m = matrix.length;
n = matrix[0].length;
this.matrix = matrix;
//用两个数组标记哪些点是能到太平洋和大西洋的
boolean[][] canReachP = new boolean[m][n];
boolean[][] canReachA = new boolean[m][n];
//对左右两条边进行dfs
for (int i = 0; i < m; i++) {
dfs(i, 0, canReachP);
dfs(i, n - 1, canReachA);
}
//对上下两条边进行dfs
for (int i = 0; i < n; i++) {
dfs(0, i, canReachP);
dfs(m - 1, i, canReachA);
}
//找交集
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (canReachA[i][j] && canReachP[i][j]){
result.add(Arrays.asList(i, j));
}
}
}
return result;
}
//dfs
private void dfs(int r, int c, boolean[][] canReach) {
//访问过的话直接返回
if (canReach[r][c]){
return;
}
//标记为true
canReach[r][c] = true;
//对四个方向进行dfs
for (int[] d : direction) {
int nextR = r + d[0];
int nextC = c + d[1];
if (nextR < 0 || nextR >= m || nextC < 0 || nextC >= n || matrix[r][c] > matrix[nextR][nextC]){
continue;
}
dfs(nextR, nextC, canReach);
}
}
}