owned this note
owned this note
Published
Linked with GitHub
# Leetcode刷題學習筆記--DFS/BFS
## DFS
自己呼叫自己達到DFS的效果。可以分成兩種形式。
1. TOP-BOTTOM 從上到下
```cpp
void dfs(node *root) {
// 先做運算
...
// 最後在呼叫自己
dfs(root->next);
}
```
2. BOTTOM-TOP 從下到上
```cpp
void dfs(node *root) {
// 先呼叫自己,則會先走訪到最後node
dfs(root->next);
// 最後在做運算判斷
...
}
```
例如 [110. Balanced Binary Tree](/Rs3M7lf1TzOR0Lx_RhgpNg#110-Balanced-Binary-Tree) 適合用bottom-top。
### 使用DFS偵測cyclic
1. 偵測graph上所有的path是否有cyclic,所以必須從每個node出發來檢查每條path。**必須從每個node出發,才可以走過每條path**
```cpp=
for(int i = 0; i < numNodes; ++i) {
if(hasCyclic(i)) return true;
}
return false;
```
2. 記錄每條path上找過的node,因為cyclic表示會走回到之前走過的node。
```cpp=
unordered_map<int, vector<int>> next;
unordered_set<int> inPath;
bool hasCyclic(int node)
{
// 碰到已經訪問過的node,有cyclic
if(inPath.count(node)) return true;
inPath.insert(node); // 紀錄訪問過的node
for(auto& n : next[node])
if(hasCyclic(n)) return true;
inPath.erase(node); // 刪除訪問過的node,往下一個path
return false;
}
```
3. 紀錄訪問過的node。例如一個path 0->2->3->4已經訪問過且確認沒cyclic,如果有也已經返回了。node 1接下來訪問node 2,因為node 2已經訪問過且沒cyclic所以就不需要再一次訪問。
```cpp=
unordered_set<int> v;
bool hasCyclic(int node)
{
if(inPath.count(node)) return true;
if(v.count(node)) return false; // 已經訪問過不會有cyclic
// 加入已經訪問過的node,雖然不確定會不會有cyclic
// 如果有,最後還是會返回true。
// 所以可以安心加入
v.insert(node);
inPath.insert(node);
for(auto& n : next[node])
if(hasCyclic(n)) return true;
inPath.erase(node);
return false;
}
```
+ [207. Course Schedule](/Rs3M7lf1TzOR0Lx_RhgpNg#207-Course-Schedule)
## BFS
使用queue來一層一層尋找答案
```cpp
queue<pair<int, int>> q;
while(!q.empty()) {
for(int i = q.size(); i > 0; --i) {
auto& p = q.front(); q.pop();
// 檢查接下來的node是否inRange且符合條件
// ** 把node做上記號避免之後重複尋找 ** //
// 將新的可能的node推進queue
q.push(newnode);
}
// update answer
}
```
例如 [1091. Shortest Path in Binary Matrix](/Rs3M7lf1TzOR0Lx_RhgpNg?both#1091-Shortest-Path-in-Binary-MatrixMedium)
除了使用queue,也可以使用priority_queue。使用priority_queue的目的是想更快的接近答案,因為每次選都是從最有可能的答案來處理。
例如 [1066. Campus Bikes II](https://leetcode.com/problems/campus-bikes-ii/description/)
```cpp=
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({0, 0});
while(!pq.empty()) {
auto [dist, mask] = pq.top(); pq.pop();
//條件成立,直接返回
int nextWorker = __builtin_popcount(mask);
if(nextWorker == workers.size()) return dist;
// 否則根據目前的條件,找尋下一個可能
for(int nextBike = 0; nextBike < bikes.size(); ++nextBike) {
if((mask &(1<< nextBike)) == 0) {
int nextDist = dist + getDist(workers[nextWorker], bikes[nextBike]);
int nextMask |= 1 << nextBike;
pq.push({nextDist, nextMask});
}
}
}
```
## Problems
### [565. Array Nesting(Medium)](https://leetcode.com/problems/array-nesting/)
依題意找出最大的集合。
```cpp=
int arrayNesting(vector<int>& nums) {
auto n = nums.size();
vector<bool> visited(n, false);
int cnt, rtn{0};
for(int start = 0; start < n; start++) {
if(visited[start]) continue;
cnt = 0;
int tmp = start;
while(!visited[tmp]) {
cnt++;
visited[tmp] = true;
tmp = nums[tmp];
}
rtn = max(rtn, cnt);
}
return rtn;
}
```
這邊需要一個額外的空間visited來存放是否訪問過,因為數字從0開始且不會重複,所以可以把數字放到和index相同的地方,就不用額外空間。
```cpp=
while (nums[i] != i) {
swap(nums[i], nums[nums[i]]);
++cnt;
}
```
### [733. Flood Fill(Easy)](https://leetcode.com/problems/flood-fill/)
給定一個起始點,把和起始點一樣數值的相鄰點塗成新的顏色。
> 這邊的重點是離開function的條件
> 1. 座標超出範圍
> 2. 已經走過
> 3. 數值和一開始的點不一樣
```cpp=
class Solution {
int n, m, orig, nc;
vector<vector<bool>> visited;
public:
void helper(vector<vector<int>>& image, int y, int x) {
if(x < 0 || y < 0 || y >= n || x >= m || visited[y][x] || image[y][x] != orig) return;
vector<pair<int, int>> steps = {{0, -1}, {-1, 0}, {0, 1}, {1, 0}};
image[y][x] = nc;
visited[y][x] = true;
for(auto step : steps) {
int newx = x + step.second;
int newy = y + step.first;
helper(image, newy, newx);
}
}
vector<vector<int>> floodFill(vector<vector<int>>& image, int sr, int sc, int newColor) {
n = image.size();
m = image[0].size();
orig = image[sr][sc];
nc = newColor;
visited = vector<vector<bool>>(n, vector<bool>(m, false));
helper(image, sr, sc);
return image;
}
};
```
### [542. 01 Matrix(Medium)](https://leetcode.com/problems/01-matrix/)
輸出一個矩陣,標示出和0的最短距離。
> 因為要找出距離0的最短距離,所以一開始先把0標註出來。
> 然後用queue把標註過的座標存起來,因為要從0開始往外擴散。
```cpp=
vector<vector<int>> updateMatrix(vector<vector<int>>& mat) {
int m = mat.size(), n = mat[0].size();
vector<vector<int>> dirs{{0,-1},{-1,0},{0,1},{1,0}};
queue<pair<int, int>> q;
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (mat[i][j] == 0) q.push({i, j});
else mat[i][j] = INT_MAX;
}
}
while (!q.empty()) {
auto t = q.front(); q.pop();
for (auto dir : dirs) {
int x = t.first + dir[0], y = t.second + dir[1];
if (x < 0 || x >= m || y < 0 || y >= n || mat[x][y] <= mat[t.first][t.second] + 1) continue;
mat[x][y] = mat[t.first][t.second] + 1;
q.push({x, y});
}
}
return mat;
}
```
另外一個重點是,因為是找最短距離。所以目標值max[x][y]小於mat[t.first][t.second] + 1也可以不用繼續比。
```cpp=
mat[x][y] <= mat[t.first][t.second] + 1
```
### [994. Rotting Oranges(Medium)](https://leetcode.com/problems/rotting-oranges/)
腐爛的橘子會繼續讓旁邊的橘子腐爛,幾回合後橘子會全部腐爛,如果還有新鮮的橘子就會傳-1。
這一題和[01 Matrix](https://hackmd.io/Rs3M7lf1TzOR0Lx_RhgpNg?view#542-01-MatrixMedium)類似,但是這邊是要算出幾回合後會全部腐爛。所以這邊用res紀錄,==每處理過一次queue就++res==。
```cpp=
while (!q.empty() && freshLeft > 0) {
for (int i = q.size(); i > 0; --i) {
......
}
++res;
}
```
用一個queue放腐爛橘子的位置。
```cpp=
queue<vector<int>> q;
```
這邊有個特別的技巧,避免使用兩個queue。因為必須知道何時處理完queue,所以==在迴圈的一開始先記錄queue的大小==,之後再開始處理。
```cpp=
for (int i = q.size(); i > 0; --i) { ... }
```
另外,必須知道最後還有沒有新鮮的橘子,所以多了一個freshLeft來記錄。最後的程式碼如下:
```cpp=
int orangesRotting(vector<vector<int>>& grid) {
int res = 0, m = grid.size(), n = grid[0].size(), freshLeft = 0;
queue<vector<int>> q;
vector<vector<int>> dirs{{-1, 0}, {0, 1}, {1, 0}, {0, -1}};
for (int i = 0; i < m; ++i) {
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1) ++freshLeft;
else if (grid[i][j] == 2) q.push({i, j});
}
}
while (!q.empty() && freshLeft > 0) {
for (int i = q.size(); i > 0; --i) {
auto cur = q.front(); q.pop();
for (int k = 0; k < 4; ++k) {
int x = cur[0] + dirs[k][0], y = cur[1] + dirs[k][1];
if (x < 0 || x >= m || y < 0 || y >= n || grid[x][y] != 1) continue;
grid[x][y] = 2;
q.push({x, y});
--freshLeft;
}
}
++res;
}
return freshLeft > 0 ? -1 : res;
}
```
### [863. All Nodes Distance K in Binary Tree](https://leetcode.com/problems/all-nodes-distance-k-in-binary-tree/)
在binary tree中,給你一個TreeNode *target,找出離target距離k的TreeNode。
> 1. 如果這題是n-ary tree而且target是root的話,就是用BFS就可以完成。
> 2. 如何把binary tree轉成 n-ary tree?
> 3. 使用一個unordered_map<TreeNode *, vector<TreeNode *>> 來記錄每個node可以到達的parent和child,即可以達到n-ary tree的效果。
> 4. 最後再用BFS來選出距離為k的TreeNode。
```cpp
class Solution {
void helper(TreeNode *root, unordered_map<TreeNode *, vector<TreeNode *>>& m) {
if(root->left) {
m[root].push_back(root->left);
m[root->left].push_back(root);
helper(root->left, m);
}
if(root->right) {
m[root].push_back(root->right);
m[root->right].push_back(root);
helper(root->right, m);
}
}
public:
vector<int> distanceK(TreeNode* root, TreeNode* target, int k) {
// special case : k == 0, 不需要尋找。
if(k == 0) return {target->val};
// 建立map來記錄每個TreeNode可以訪問的node,包括parent和child
unordered_map<TreeNode *, vector<TreeNode *>> m;
helper(root, m);
// 因為target是pointer所以不用找,直接把target push到queue裡面。
queue<TreeNode *> q;
q.push(target);
// 使用visited避免重複訪問TreeNode
unordered_set<TreeNode *> visited;
visited.insert(target);
// 如果queue有東西且k還沒到0
while(!q.empty() && k != 0) {
k--;
for(int i = q.size(); i > 0; --i) {
TreeNode *p = q.front(); q.pop();
visited.insert(p);
for(auto& n : m[p]) {
if(!visited.count(n))
q.push(n);
}
}
}
// output
if(k != 0) return {};
vector<int> rtn;
while(!q.empty()) {
rtn.push_back(q.front()->val);
q.pop();
}
return rtn;
}
};
```
### [110. Balanced Binary Tree](https://leetcode.com/problems/balanced-binary-tree/)
```cpp
class Solution {
// bottom up solution
int heigh(TreeNode *root) {
if(!root) return 0;
// 因為先呼叫自己,所以會先找到最後一個node
int l = heigh(root->left);
int r = heigh(root->right);
// 先判斷是否為-1,都不是才比較兩邊的差
if(l == -1 || r == -1 || abs(l - r) > 1) return -1;
return max(l, r) + 1;
}
public:
bool isBalanced(TreeNode* root) {
if(!root) return true;
return heigh(root) != -1;
}
};
```
### [1091. Shortest Path in Binary Matrix(Medium)](https://leetcode.com/problems/shortest-path-in-binary-matrix/)
找出從(0, 0)->(n-1, n-1)的最短路徑。
```cpp
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
if(grid[0][0]) return -1;
int n = grid.size();
vector<vector<int>> dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0},
{1, 1}, {1, -1}, {-1, -1}, {-1, 1}};
queue<vector<int>> q;
q.push({0, 0});
grid[0][0] = 1;
int dist = 1;
while(!q.empty()) {
for(int i = q.size(); i > 0; --i) {
int y = q.front()[0];
int x = q.front()[1];
q.pop();
if(y == n - 1 && x == n - 1) return dist;
for(auto& d : dirs) {
int newy = y + d[0];
int newx = x + d[1];
if(newy < 0 || newx < 0
|| newy == n || newx == n
|| grid[newy][newx]) continue;
// 先做上記號,避免之後重複尋找
grid[newy][newx] = 1;
q.push({newy, newx});
}
}
dist++;
}
return -1;
}
```
### [207. Course Schedule](https://leetcode.com/problems/course-schedule/description/)
```cpp=
class Solution {
vector<vector<int>> adj;
vector<int> v, inPath;
bool hasCyclic(int node) {
if(inPath[node]) return true;
if(v[node]) return false;
v[node] = 1; // 訪問過的node
inPath[node] = 1;
for(auto& n : adj[node]) {
if(hasCyclic(n)) return true; // 訪問過的node有cyclic就會return true
}
inPath[node] = 0;
return false;
}
public:
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
adj.resize(numCourses, vector<int>());
v.resize(numCourses);
inPath.resize(numCourses);
for(auto& r : prerequisites)
adj[r[1]].push_back(r[0]);
for(int i = 0; i < numCourses; ++i)
if(hasCyclic(i)) return false;
return true;
}
};
```
###### tags: `leetcode` `刷題`