搜索
深度優先搜索和廣度優先搜索是常考題型,廣泛運用於樹和圖中。
BFS
廣度優先搜索一層一層地進行遍歷,每層遍歷都以上一層遍歷的結果作爲起點,遍歷一個距離能訪問到的所有節點。需要注意的是,遍歷過的節點不能再次被遍歷。
第一層:
- 0 -> {6,2,1,5}
第二層:
- 6 -> {4}
- 2 -> {}
- 1 -> {}
- 5 -> {3}
第三層:
- 4 -> {}
- 3 -> {}
每一層遍歷的節點都與根節點距離相同。設 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;
}
最短單詞路徑
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;
}
}
DFS
廣度優先搜索一層一層遍歷,每一層得到的所有新節點,要用隊列存儲起來以備下一層遍歷的時候再遍歷。
而深度優先搜索在得到一個新節點時立即對新節點進行遍歷:從節點 0 出發開始遍歷,得到到新節點 6 時,立馬對新節點 6 進行遍歷,得到新節點 4;如此反覆以這種方式遍歷新節點,直到沒有新節點了,此時返回。返回到根節點 0 的情況是,繼續對根節點 0 進行遍歷,得到新節點 2,然後繼續以上步驟。
從一個節點出發,使用 DFS 對一個圖進行遍歷時,能夠遍歷到的節點都是從初始節點可達的,DFS 常用來求解這種 可達性 問題。
在程序實現 DFS 時需要考慮以下問題:
- 棧:用棧來保存當前節點信息,當遍歷新節點返回時能夠繼續遍歷當前節點。可以使用遞歸棧。
- 標記:和 BFS 一樣同樣需要對已經遍歷過的節點進行標記。
查找最大的連通面積
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);
}
}
}
}
好友關係的連通分量數目
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);
}
}
}
}