廣度優先搜尋相關面試演算法總結(非圖論方面)
文章目錄
前言
此處總結的廣度優先搜尋相關面試演算法題前三道主要側重在樹的各種“層序遍歷”上,後面幾道則側重在對二維矩陣進行一些的操作,從而求得相應的值或矩陣,類似“被圍繞的區域”,“最短的橋”,“腐爛的橘子”等類似題目。其幾乎有統一的模板,文章後面會總結呈現給大家,重點在於領會廣度優先搜尋的演算法思想。
演算法題
1. LeetCode 劍指 Offer 32 - III : 從上到下列印二叉樹 III
請實現一個函式按照之字形順序列印二叉樹,即第一行按照從左到右的順序列印,第二層按照從右到左的順序列印,第三行再按照從左到右的順序列印,其他行以此類推。
例如:
給定二叉樹: [3,9,20,null,null,15,7],
返回其層次遍歷結果:
[
[3],
[20,9],
[15,7]
]
提示:
節點總數 <= 1000
/*
* 1
* LeetCode 劍指 Offer 32 - III : 從上到下列印二叉樹 III
* https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/
*/
vector<vector<int>> levelOrder(TreeNode* root)
{
vector<vector<int> > res;
if(root == nullptr)
{
return res;
}
queue<TreeNode*> q;
q.push(root);
int i = 1;
while(!q.empty())
{
int num = q.size();
vector<int> tmp;
for(int i = 0; i < num; i++)
{
TreeNode* node = q.front();
q.pop();
tmp.push_back(node->val);
if(node->left != nullptr) {
q.push(node->left);
}
if(node->right != nullptr) {
q.push(node->right);
}
}
if((i++%2) == 0)
{
reverse(tmp.begin(), tmp.end());
}
res.push_back(tmp);
}
return res;
}
2. LeetCode 429 : N叉樹的層序遍歷
給定一個 N 叉樹,返回其節點值的層序遍歷。 (即從左到右,逐層遍歷)。
說明:
樹的深度不會超過 1000。
樹的節點總數不會超過 5000。
/*
* 2
* LeetCode 429 : N叉樹的層序遍歷
* https://leetcode-cn.com/problems/n-ary-tree-level-order-traversal/
*/
vector<vector<int>> levelOrder(Node* root)
{
vector<vector<int> > res;
vector<int> tmp;
if(root == NULL)
return res;
queue<Node*> q;
q.push(root);
while(!q.empty())
{
tmp.clear();
int count = q.size();
while(count > 0)
{
Node* node = q.front();
tmp.push_back(node->val);
q.pop();
count--;
if(node->children.size() != 0)
for(int i = 0; i < node->children.size(); i++)
q.push(node->children[i]);
}
res.push_back(tmp);
}
return res;
}
3. LeetCode 103. 二叉樹的鋸齒形層次遍歷
給定一個二叉樹,返回其節點值的鋸齒形層次遍歷。(即先從左往右,再從右往左進行下一層遍歷,以此類推,層與層之間交替進行)。
/*
* 3
* LeetCode 103. 二叉樹的鋸齒形層次遍歷
* https://leetcode-cn.com/problems/binary-tree-zigzag-level-order-traversal/
*/
vector<vector<int>> zigzagLevelOrder(TreeNode* root)
{
vector<vector<int> > res;
if(root == NULL)
return res;
queue<TreeNode*> q;
q.push(root);
int level = 0;
while(!q.empty())
{
vector<int> tmp;
int count = q.size();
for(int i = 0; i < count; i++)
{
TreeNode *t=q.front();
q.pop();
if(level%2 == 0)
tmp.push_back(t->val);
else
tmp.insert(tmp.begin(),t->val);
if(t->left)
q.push(t->left);
if(t->right)
q.push(t->right);
}
level++;
res.push_back(tmp);
}
return res;
}
4. LeetCode 130 : 被圍繞的區域
給定一個二維的矩陣,包含 ‘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’。如果兩個元素在水平或垂直方向相鄰,則稱它們是“相連”的。
/*
* 4
* LeetCode 130 : 被圍繞的區域
* https://leetcode-cn.com/problems/surrounded-regions/
*/
const int dx[4] = {1, -1, 0, 0};
const int dy[4] = {0, 0, 1, -1};
void solve(vector<vector<char>>& board)
{
int n = board.size();
if(n == 0) {
return;
}
int m = board[0].size();
queue<pair<int, int>> que;
for(int i = 0; i < n; i++) {
if(board[i][0] == 'O') {
que.emplace(i, 0);
}
if(board[i][m - 1] == 'O') {
que.emplace(i, m - 1);
}
}
for(int i = 1; i < m - 1; i++) {
if(board[0][i] == 'O') {
que.emplace(0, i);
}
if(board[n - 1][i] == 'O') {
que.emplace(n - 1, i);
}
}
while(!que.empty()) {
int x = que.front().first, y = que.front().second;
que.pop();
board[x][y] = 'A';
for(int i = 0; i < 4; i++) {
int mx = x + dx[i], my = y + dy[i];
if(mx < 0 || my < 0 || mx >= n || my >= m || board[mx][my] != 'O') {
continue;
}
que.emplace(mx, my);
}
}
for(int i = 0; i < n; i++) {
for(int j = 0; j < m; j++) {
if(board[i][j] == 'A') {
board[i][j] = 'O';
} else if(board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
5. LeetCode 934 : 最短的橋
在給定的二維二進位制陣列 A 中,存在兩座島。(島是由四面相連的 1 形成的一個最大組。)
現在,我們可以將 0 變為 1,以使兩座島連線起來,變成一座島。
返回必須翻轉的 0 的最小數目。(可以保證答案至少是 1。)
示例 1:
輸入:[[0,1],[1,0]]
輸出:1
示例 2:
輸入:[[0,1,0],[0,0,0],[0,0,1]]
輸出:2
示例 3:
輸入:[[1,1,1,1,1],[1,0,0,0,1],[1,0,1,0,1],[1,0,0,0,1],[1,1,1,1,1]]
輸出:1
提示:
1 <= A.length = A[0].length <= 100
A[i][j] == 0 或 A[i][j] == 1
Method 1 :DFS + BFS
- 找到第一個1的位置。
- 使用 DFS 找出所有相連的1, 並將所有相連的1都放入到一個佇列 queue 中,並且將該點的值改為2。
- 使用 BFS 進行層序遍歷,每遍歷一層,結果 res 都增加1,當遇到1時,直接返回 res 即可。
/*
* 5
* LeetCode 934 : 最短的橋
* https://leetcode-cn.com/problems/shortest-bridge/
*/
/* Method 1 : DFS + BFS
1. 找到第一個1的位置。
2. 使用 DFS 找出所有相連的1, 並將所有相連的1都放入到一個佇列 queue 中,並且將該點的值改為2。
3. 使用 BFS 進行層序遍歷,每遍歷一層,結果 res 都增加1,當遇到1時,直接返回 res 即可。
*/
void dfs(vector<vector<int> >& A, int x, int y, queue<int>& q)
{
int n = A.size();
if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2)
return;
A[x][y] = 2;
q.push(x * n + y);
dfs(A, x + 1, y, q);
dfs(A, x, y + 1, q);
dfs(A, x - 1, y, q);
dfs(A, x, y - 1, q);
}
int shortestBridge(vector<vector<int> >& A)
{
int res = 0, n = A.size(), startX = -1, startY = -1;
queue<int> q;
vector<vector<int> > dirs = {{-1,0},{0,1},{1,0},{0,-1}};
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(A[i][j] == 0)
continue;
startX = i; startY = j;
break;
}
if(startX != -1)
break;
}
dfs(A, startX, startY, q);
while(!q.empty())
{
for(int i = q.size(); i > 0; i--)
{
int t = q.front();
q.pop();
for(int k = 0; k < dirs.size(); k++)
{
int x = t / n + dirs[k][0];
int y = t % n + dirs[k][1];
if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2)
continue;
if(A[x][y] == 1)
return res;
A[x][y] = 2;
q.push(x * n + y);
}
}
++res;
}
return res;
}
Method 2:BFS + BFS
- 找到第一個1的位置。
- 使用 BFS 找出所有相連的1, 並將所有相連的1都放入到一個佇列 queue 中,並且將該點的值改為2。
- 使用 BFS 進行層序遍歷,每遍歷一層,結果 res 都增加1,當遇到1時,直接返回 res 即可。
/* Method 2
1. 找到第一個1的位置。
2. 使用 BFS 找出所有相連的1, 並將所有相連的1都放入到一個佇列 queue 中,並且將該點的值改為2。
3. 使用 BFS 進行層序遍歷,每遍歷一層,結果 res 都增加1,當遇到1時,直接返回 res 即可。
**注意 :第一個 BFS 不需要是層序遍歷的,而第二個 BFS 是必須層序遍歷。
*/
int shortestBridge(vector<vector<int>>& A)
{
int res = 0, n = A.size();
queue<int> que; // first BFS used
queue<int> q; // secod=nd BFS used
vector<vector<int> > dirs = {{-1,0},{0,1},{1,0},{0,-1}};
for(int i = 0; i < n; i++)
{
for(int j = 0; j < n; j++)
{
if(A[i][j] == 0)
continue;
A[i][j] = 2;
que.push(i * n + j);
break;
}
if(!que.empty())
break;
}
// first BFS
while(!que.empty())
{
int t = que.front();
que.pop();
q.push(t);
for(int k = 0; k < 4; k++)
{
int x = t / n + dirs[k][0];
int y = t % n + dirs[k][1];
if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 0 || A[x][y] == 2)
continue;
A[x][y] = 2;
que.push(x * n + y);
}
}
// second BFS
while(!q.empty())
{
for(int i = q.size(); i > 0; i--)
{
int t = q.front();
q.pop();
for(int k = 0; k < 4; k++)
{
int x = t / n + dirs[k][0];
int y = t % n + dirs[k][1];
if(x < 0 || x >= n || y < 0 || y >= n || A[x][y] == 2)
continue;
if(A[x][y] == 1)
return res;
A[x][y] = 2;
q.push(x * n + y);
}
}
++res;
}
return res;
}
6. LeetCode 994 : 腐爛的橘子
在給定的網格中,每個單元格可以有以下三個值之一:
值 0 代表空單元格;
值 1 代表新鮮橘子;
值 2 代表腐爛的橘子。
每分鐘,任何與腐爛的橘子(在 4 個正方向上)相鄰的新鮮橘子都會腐爛。
返回直到單元格中沒有新鮮橘子為止所必須經過的最小分鐘數。如果不可能,返回 -1。
示例 1:
輸入:[[2,1,1],[1,1,0],[0,1,1]]
輸出:4
示例 2:
輸入:[[2,1,1],[0,1,1],[1,0,1]]
輸出:-1
解釋:左下角的橘子(第 2 行, 第 0 列)永遠不會腐爛,因為腐爛只會發生在 4 個正向上。
示例 3:
輸入:[[0,2]]
輸出:0
解釋:因為 0 分鐘時已經沒有新鮮橘子了,所以答案就是 0 。
提示:
1 <= grid.length <= 10
1 <= grid[0].length <= 10
grid[i][j] 僅為 0、1 或 2
/*
* 6
* LeetCode 994 : 腐爛的橘子
* https://leetcode-cn.com/problems/rotting-oranges/
*/
int orangesRotting(vector<vector<int> >& grid)
{
int ct = 0, res = -1;
queue<vector<int> > q;
vector<vector<int> > dir = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
for(int i = 0; i < grid.size(); i++)
{
for(int j = 0; j < grid[i].size(); j++)
{
if(grid[i][j] > 0)
ct++;
if(grid[i][j] == 2)
q.push({i, j});
}
}
while(!q.empty())
{
res++;
int n = q.size();
for(int k = 0; k < n; k++)
{
vector<int> cur = q.front();
ct--;
q.pop();
for(int i = 0; i < dir.size(); i++)
{
int x = cur[0] + dir[i][0];
int y = cur[1] + dir[i][1];
if(x >= grid.size() || x < 0 || y >= grid[0].size() || y < 0 || grid[x][y] != 1)
continue;
grid[x][y] = 2;
q.push({x, y});
}
}
}
if(ct == 0)
return max(0, res);
return -1;
}
7. LeetCode 542 : 01 矩陣
給定一個由 0 和 1 組成的矩陣,找出每個元素到最近的 0 的距離。
兩個相鄰元素間的距離為 1 。
示例 1:
輸入:
0 0 0
0 1 0
0 0 0
輸出:
0 0 0
0 1 0
0 0 0
示例 2:
輸入:
0 0 0
0 1 0
1 1 1
輸出:
0 0 0
0 1 0
1 2 1
注意:
給定矩陣的元素個數不超過 10000。
給定矩陣中至少有一個元素是 0。
矩陣中的元素只在四個方向上相鄰: 上、下、左、右。
/*
* 7
* LeetCode 542 : 01 矩陣
* https://leetcode-cn.com/problems/01-matrix/
*/
/*
BFS:
從每一個0位置向上下左右四個方向進行廣播,如果廣播到的位置距離0的距離大於當前位置距離0的距離加一,
則將廣播到的位置距離0的位置更新為當前位置距離0的距離加一。
*/
vector<vector<int>> updateMatrix(vector<vector<int>>& matrix)
{
int R = matrix.size();
if(R == 0)
return matrix;
int C = matrix[0].size();
vector<vector<int> > dist(R, vector<int>(C, INT_MAX));
queue<pair<int,int> > q;
for(int i = 0; i < R; i++)
{
for(int j = 0; j < C; j++)
{
if(matrix[i][j] == 0)
{
dist[i][j] = 0;
q.push({i, j});
}
}
}
vector<vector<int> > dir = {{-1,0},{0,1},{1,0},{0,-1}};
while(!q.empty())
{
pair<int, int> curr = q.front();
q.pop();
for(int i = 0; i < 4; i++)
{
int new_r = curr.first + dir[i][0];
int new_c = curr.second + dir[i][1];
if(new_r >= 0 && new_c >= 0 && new_r < R && new_c < C)
{
if(dist[new_r][new_c] > dist[curr.first][curr.second] + 1)
{
dist[new_r][new_c] = dist[curr.first][curr.second] + 1;
q.push({ new_r, new_c });
}
}
}
}
return dist;
}
8. LeetCode 1293 : 網格中的最短路徑(Hard)
給你一個 m * n 的網格,其中每個單元格不是 0(空)就是 1(障礙物)。每一步,您都可以在空白單元格中上、下、左、右移動。
如果您 最多 可以消除 k 個障礙物,請找出從左上角 (0, 0) 到右下角 (m-1, n-1) 的最短路徑,並返回通過該路徑所需的步數。如果找不到這樣的路徑,則返回 -1。
示例 1:
輸入:
grid =
[[0,0,0],
[1,1,0],
[0,0,0],
[0,1,1],
[0,0,0]],
k = 1
輸出:6
解釋:
不消除任何障礙的最短路徑是 10。
消除位置 (3,2) 處的障礙後,最短路徑是 6 。該路徑是 (0,0) -> (0,1) -> (0,2) -> (1,2) -> (2,2) -> (3,2) -> (4,2).
示例 2:
輸入:
grid =
[[0,1,1],
[1,1,1],
[1,0,0]],
k = 1
輸出:-1
解釋:
我們至少需要消除兩個障礙才能找到這樣的路徑。
提示:
grid.length == m
grid[0].length == n
1 <= m, n <= 40
1 <= k <= m*n
grid[i][j] == 0 or 1
grid[0][0] == grid[m-1][n-1] == 0
/*
* Hard
* LeetCode 1293 : 網格中的最短路徑
* https://leetcode-cn.com/problems/shortest-path-in-a-grid-with-obstacles-elimination/
*/
struct Nagato
{
int x, y;
int rest;
Nagato(int _x, int _y, int _r): x(_x), y(_y), rest(_r) {}
};
class Solution
{
public:
vector<vector<int> > dirs = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int shortestPath(vector<vector<int>>& grid, int k)
{
if(grid.empty() || grid[0].empty())
{
return 0;
}
int m = grid.size(), n = grid[0].size();
if(m == 1 && n == 1)
{
return 0;
}
k = min(k, m + n - 3);
vector<vector<vector<bool> > > vis(m, vector<vector<bool> >(n, vector<bool>(k+1, false)));
queue<Nagato> q;
q.push(Nagato(0, 0, k));
vis[0][0][k] = true;
for(int step = 1; q.size() > 0; ++step)
{
int cnt = q.size();
for(int j = 0; j < cnt; ++j)
{
Nagato cur = q.front();
q.pop();
for(int i = 0; i < dirs.size(); ++i)
{
int nx = cur.x + dirs[i][0];
int ny = cur.y + dirs[i][1];
if(nx >= 0 && nx < m && ny >= 0 && ny < n)
{
if(grid[nx][ny] == 0 && !vis[nx][ny][cur.rest]) {
if(nx == m - 1 && ny == n - 1) {
return step;
}
q.push(Nagato(nx, ny, cur.rest));
vis[nx][ny][cur.rest] = true;
} else if(grid[nx][ny] == 1 && cur.rest > 0 && !vis[nx][ny][cur.rest - 1]) {
q.push(Nagato(nx, ny, cur.rest - 1));
vis[nx][ny][cur.rest - 1] = true;
}
}
}
}
}
return -1;
}
};
注:最後一題“LeetCode 1293 : 網格中的最短路徑”,難度比較大,答案程式碼摘抄官方解答。
模版
/*
*模版
*/
queue<T> q;
while(!q.empty())
{
...//
int n = q.size();
for(int k = 0; k < n; k++)
{
T t = q.front();
...//
q.pop();
for(擴充套件方式)
{
if(擴充套件方式所達到狀態合法)
{
....//根據題意來新增
q.push();
}
}
}
}
總結
以上是廣度優先搜尋相關面試演算法題彙總,個別題目也給出瞭解題思路和註釋。
所有程式碼都可以去我的GitHub網站檢視,後續也將繼續補充其他演算法方面的相關題目,也會單獨寫一篇有關圖的面試演算法題文章。
相關文章
- 基本演算法——深度優先搜尋(DFS)和廣度優先搜尋(BFS)演算法
- 【演算法】廣度/寬度優先搜尋(BFS)演算法
- 演算法(三):圖解廣度優先搜尋演算法演算法圖解
- 演算法筆記(廣度優先搜尋)演算法筆記
- 深度和廣度優先搜尋演算法演算法
- 圖的廣度優先搜尋和深度優先搜尋Python實現Python
- 演算法競賽——BFS廣度優先搜尋演算法
- c++ 廣度優先搜尋(寬搜)C++
- 廣度優先搜尋(BFS)思路及演算法分析演算法
- 《圖論》——深度優先搜尋演算法(DFS)圖論演算法
- python 二叉樹深度優先搜尋和廣度優先搜尋Python二叉樹
- js版本的(廣、深)度優先搜尋JS
- 《圖論》——廣度優先遍歷演算法(BFS)圖論演算法
- BFS-圖的廣度優先搜尋--鄰接矩陣矩陣
- Swift 演算法實戰之路:深度和廣度優先搜尋Swift演算法
- 【程式碼隨想錄】廣度優先搜尋
- 廣度優先搜尋,分支限界- ZOJ - 1136 Multiple
- 啟發式搜尋的方式(深度優先,廣度優先)和 搜尋方法(Dijkstra‘s演算法,代價一致搜尋,貪心搜尋 ,A星搜尋)演算法
- Android程式設計師面試會遇到的演算法(part 2 廣度優先搜尋)Android程式設計師面試演算法
- 0演算法基礎學演算法 搜尋篇第二講 BFS廣度優先搜尋的思想演算法
- 「Golang成長之路」迷宮的廣度優先搜尋Golang
- golang學習筆記——迷宮的廣度優先搜尋Golang筆記
- BFS廣度優先搜尋(10)--fzu2150(基礎題)
- BFS廣度優先搜尋(6)--poj3414(基礎題)
- python資料結構之圖深度優先和廣度優先Python資料結構
- BFS廣度優先搜尋(11)--hdu2102(基礎題)
- 廣度優先遍歷圖解圖解
- Python 圖_系列之基於鄰接炬陣實現廣度、深度優先路徑搜尋演算法Python演算法
- 【演算法】深度優先搜尋(DFS)演算法
- BFS廣度優先搜尋(4)--hdu2717(poj3278)(基礎題)
- MySQL單詞搜尋相關度排名MySql
- 演算法總結--搜尋演算法
- 搜尋演算法總結演算法
- 再來一篇深度優先遍歷/搜尋總結?
- 深度優先搜尋演算法-dfs講解演算法
- 深度優先搜尋演算法(DFS)講解演算法
- BFS廣度優先搜尋(3)--poj2251(zoj1940)(基礎題)
- 深度DFS 和 廣度BFS搜尋演算法學習演算法