深度优先搜索和广度优先搜索是常考题型,广泛运用于树和图中。
广度优先搜索一层一层地进行遍历,每层遍历都以上一层遍历的结果作为起点,遍历一个距离能访问到的所有节点。需要注意的是,遍历过的节点不能再次被遍历。
第一层:
第二层:
第三层:
每一层遍历的节点都与根节点距离相同。设 di 表示第 i 个节点与根节点的距离,推导出一个结论:对于先遍历的节点 i 与后遍历的节点 j,有 di <= dj。利用这个结论,可以求解最短路径等 最优解 问题:第一次遍历到目的节点,其所经过的路径为最短路径。应该注意的是,使用 BFS 只能求解无权图的最短路径。
在程序实现 BFS 时需要考虑以下问题:
计算在网格中从原点到特定点的最短路径长度
[[1,1,0,1],
[1,0,1,0],
[1,1,1,1],
[1,0,1,1]]
1 表示可以经过某个位置,求解从 (0, 0) 位置到 (tr, tc) 位置的最短路径长度。
public int minPathLength(int[][] grids, int tr, int tc) {
final int[][] direction = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
final int m = grids.length, n = grids[0].length;
Queue<Pair<Integer, Integer>> queue = new LinkedList<>();
queue.add(new Pair<>(0, 0));
int pathLength = 0;
while (!queue.isEmpty()) {
int size = queue.size();
pathLength++;
while (size-- > 0) {
Pair<Integer, Integer> cur = queue.poll();
int cr = cur.getKey(), cc = cur.getValue();
grids[cr][cc] = 0; // 标记
for (int[] d : direction) {
int nr = cr + d[0], nc = cc + d[1];
if (nr < 0 || nr >= m || nc < 0 || nc >= n || grids[nr][nc] == 0) {
continue;
}
if (nr == tr && nc == tc) {
return pathLength;
}
queue.add(new Pair<>(nr, nc));
}
}
}
return -1;
}
最短单词路径
127. Word Ladder (Medium)
Input:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
Output: 5
Explanation: As one shortest transformation is "hit" -> "hot" -> "dot" -> "dog" -> "cog",
return its length 5.
Input:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
Output: 0
Explanation: The endWord "cog" is not in wordList, therefore no possible transformation.
题目描述:找出一条从 beginWord 到 endWord 的最短路径,每次移动规定为改变一个字符,并且改变之后的字符串必须在 wordList 中。
class Solution {
public int ladderLength(String beginWord, String endWord, List<String> wordList) {
if (null == beginWord || 0 == beginWord.length() || null == endWord || 0 == endWord.length()) {
return 0;
}
wordList.add(beginWord);
int n = wordList.size();
int start = n - 1;
int end = 0;
for (String str : wordList) {
if (!endWord.equals(str)) {
end++;
} else {
break;
}
}
if (n == end) {
return 0;
}
List<Integer>[] graph = getList(wordList);
return getPath(graph, start, end);
}
public int getPath(List<Integer>[] graph, int start, int end) {
int n = graph.length;
Queue<Integer> queue = new LinkedList<>();
boolean[] marked = new boolean[n];
marked[start] = true;
queue.add(start);
int path = 1;
while (!queue.isEmpty()) {
int size = queue.size();
path++;
while (size-- > 0) {
int position = queue.poll();
for (int next : graph[position]) {
if (next == end) {
return path;
}
if (marked[next]) {
continue;
}
marked[next] = true;
queue.add(next);
}
}
}
return 0;
}
public List<Integer>[] getList(List<String> wordList) {
int n = wordList.size();
List<Integer>[] graph = new List[n];
for (int i = 0; i < n; i++) {
graph[i] = new ArrayList<>();
for (int j = 0; j < n; j++) {
String temp = wordList.get(j);
if (connect(wordList.get(i), temp)) {
graph[i].add(j);
}
}
}
return graph;
}
public boolean connect(String s1, String s2) {
int difference = 0;
for (int i = 0; i < s1.length(); i++) {
if (s1.charAt(i) != s2.charAt(i)) {
difference++;
}
}
return difference == 1;
}
}
广度优先搜索一层一层遍历,每一层得到的所有新节点,要用队列存储起来以备下一层遍历的时候再遍历。
而深度优先搜索在得到一个新节点时立即对新节点进行遍历:从节点 0 出发开始遍历,得到到新节点 6 时,立马对新节点 6 进行遍历,得到新节点 4;如此反复以这种方式遍历新节点,直到没有新节点了,此时返回。返回到根节点 0 的情况是,继续对根节点 0 进行遍历,得到新节点 2,然后继续以上步骤。
从一个节点出发,使用 DFS 对一个图进行遍历时,能够遍历到的节点都是从初始节点可达的,DFS 常用来求解这种 可达性 问题。
在程序实现 DFS 时需要考虑以下问题:
查找最大的连通面积
695. Max Area of Island (Easy)
[[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]]
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int cur = 0;
public int maxAreaOfIsland(int[][] grid) {
if (null == grid || 0 == grid.length) {
return 0;
}
int m = grid.length;
int n = grid[0].length;
int max = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (1 == grid[i][j]) {
cur = 0;
dfs(grid, i, j);
max = Math.max(max, cur);
}
}
}
return max;
}
public void dfs(int[][] grid, int x, int y) {
int m = grid.length;
int n = grid[0].length;
if (grid[x][y] == 0) {
return ;
} else if (grid[x][y] == 1) {
grid[x][y] = 0;
cur++;
}
for (int i = 0; i < 4; i++) {
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx >= 0 && xx < m && yy >= 0 && yy < n) {
dfs(grid, xx, yy);
}
}
}
}
矩阵中的连通分量数目
200. Number of Islands (Medium)
Input:
11000
11000
00100
00011
Output: 3
可以将矩阵表示看成一张有向图。
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public int numIslands(char[][] grid) {
if (null == grid || 0 == grid.length) {
return 0;
}
int m = grid.length;
int n = grid[0].length;
int islands = 0;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == '1') {
dfs(grid, i, j);
islands++;
}
}
}
return islands;
}
public void dfs(char[][] grid, int x, int y) {
int m = grid.length;
int n = grid[0].length;
if ('0' == grid[x][y]) {
return ;
} else if ('1' == grid[x][y]){
grid[x][y] = '0';
}
for (int i = 0; i < 4; i++) {
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx >= 0 && xx < m && yy >= 0 && yy < n) {
dfs(grid, xx, yy);
}
}
}
}
好友关系的连通分量数目
547. Friend Circles (Medium)
Input:
[[1,1,0],
[1,1,0],
[0,0,1]]
Output: 2
Explanation:The 0th and 1st students are direct friends, so they are in a friend circle.
The 2nd student himself is in a friend circle. So return 2.
题目描述:好友关系可以看成是一个无向图,例如第 0 个人与第 1 个人是好友,那么 M[0][1] 和 M[1][0] 的值都为 1。
class Solution {
public int findCircleNum(int[][] M) {
if (null == M || 0 == M.length) {
return 0;
}
int m = M.length;
int circles = 0;
boolean[] visit = new boolean[m];
for (int i = 0; i < m; i++) {
if (!visit[i]) {
dfs(M, i, visit);
circles++;
}
}
return circles;
}
public void dfs(int[][] M, int i, boolean[] visit) {
visit[i] = true;
int m = M.length;
for (int k = 0; k < m; k++) {
if (!visit[k] && 1 == M[i][k]) {
dfs(M, k, visit);
}
}
}
}
填充封闭区域
130. Surrounded Regions (Medium)
For example,
X X X X
X O O X
X X O X
X O X X
After running your function, the board should be:
X X X X
X X X X
X X X X
X O X X
题目描述:使被 ‘X’ 包围的 ‘O’ 转换为 ‘X’。
先将最外侧一层搜索一遍,再处理里层。
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
public void solve(char[][] board) {
if (null == board || 0 == board.length) {
return ;
}
int m = board.length;
int n = board[0].length;
for (int i = 0; i < m; i++) {
dfs(board, i, 0);
dfs(board, i, n - 1);
}
for (int i = 1; i < n - 1; i++) {
dfs(board, 0, i);
dfs(board, m - 1, i);
}
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (board[i][j] == 'A') {
board[i][j] = 'O';
} else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
public void dfs(char[][] board, int x, int y) {
int m = board.length;
int n = board[0].length;
if (board[x][y] != 'O') {
return ;
}
board[x][y] = 'A';
for (int i = 0; i < 4; i++) {
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx >= 0 && xx < m && yy >= 0 && yy < n) {
dfs(board, xx, yy);
}
}
}
}
能到达的太平洋和大西洋的区域
417. Pacific Atlantic Water Flow (Medium)
Given the following 5x5 matrix:
Pacific ~ ~ ~ ~ ~
~ 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 *
* * * * * Atlantic
Return:
[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (positions with parentheses in above matrix).
左边和上边是太平洋,右边和下边是大西洋,内部的数字代表海拔,海拔高的地方的水能够流到低的地方,求解水能够流到太平洋和大西洋的所有位置。
class Solution {
int[][] dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
boolean[][] Pacific;
boolean[][] Atlantic;
List<int[]> list;
public List<int[]> pacificAtlantic(int[][] matrix) {
list = new ArrayList<>();
if (null == matrix || 0 == matrix.length) {
return list;
}
int m = matrix.length;
int n = matrix[0].length;
Pacific = new boolean[m][n];
Atlantic = new boolean[m][n];
for (int i = 0; i < n; i++) {
dfs(matrix, 0, i, Pacific);
}
for (int i = 1; i < m; i++) {
dfs(matrix, i, 0, Pacific);
}
for (int i = 0; i < m; i++) {
dfs(matrix, i, n - 1, Atlantic);
}
for (int i = 0; i < n - 1; i++) {
dfs(matrix, m - 1, i, Atlantic);
}
for (int i = 0; i < m ; i++) {
for (int j = 0; j < n; j++) {
if (Pacific[i][j] && Atlantic[i][j]) {
list.add(new int[]{i, j});
}
}
}
return list;
}
public void dfs(int[][] matrix, int x, int y, boolean[][] vis) {
if (vis[x][y]) {
return ;
}
vis[x][y] = true;
int m = matrix.length;
int n = matrix[0].length;
for (int i = 0; i < 4; i++) {
int xx = x + dir[i][0];
int yy = y + dir[i][1];
if (xx >= 0 && xx < m && yy >= 0 && yy < n && matrix[xx][yy] >= matrix[x][y]) {
dfs(matrix, xx, yy, vis);
}
}
}
}