一、广度优先搜索BFS
1. 二进制矩阵中的最短路径 难度:medium Leetcode题号:1091
题目描述:
思路:典型的BFS,每次向外扩一圈,当第一次到达目标点时即为所求的最短路径长度
C++代码如下:
class Solution {
public:
int shortestPathBinaryMatrix(vector<vector<int>>& grid) {
if(grid.size() == 0 || grid[0].size() == 0 || grid[0][0] == 1){
return -1;
}
int dir[8][2] = {{1,-1}, {1,0}, {1,1}, {0,1}, {0,-1}, {-1,1}, {-1,0}, {-1,-1}};
int m = grid.size();
int n = grid[0].size();
bool visit[m][n];
memset(visit,false,sizeof(visit));
queue<pair<int,int>> Q;
Q.push(make_pair(0,0));
visit[0][0] = true;
int len = 0;
while(!Q.empty()){
int size = Q.size();
len++;
while(size-- > 0){
int cur_x = Q.front().first;
int cur_y = Q.front().second;
Q.pop();
for(int i = 0 ; i < 8; i++){
int next_x = cur_x + dir[i][0];
int next_y = cur_y + dir[i][1];
if(next_x == n && next_y == m){
return len;
}
if(next_x >= 0 && next_x < n && next_y >=0 && next_y < m && grid[next_x] [next_y] == 0 && !visit[next_x][next_y]){
Q.push(make_pair(next_x, next_y));
visit[next_x][next_y] = true;
}
}
}
}
return -1;
}
};
2. 完全平方数 难度:medium Leetcode题号:279
题目描述:
思路:
把每个数看成图中的一个节点,若两个数的差为一个平方数,则这两个数对应的节点之间就有一条边,则该问题转变为求从数n对应的节点到数0对应的节点之间的最短路径长度
代码中的generateSquares()函数的作用是生成所有小于等于n的平方数
代码如下:
vector<int> generateSquares(int n){ //生成平方数
int num = 1;
int square = pow(num, 2);
vector<int> res;
while(square <= n){
res.push_back(square);
num++;
square = pow(num, 2);
}
return res;
}
class Solution {
public:
int numSquares(int n) {
vector<int> squares = generateSquares(n);
queue<int> Q;
bool visit[n+1];
memset(visit, false, sizeof(visit));
int level = 0; //层数,即组成和的平方数个数
Q.push(n);
visit[n] = true;
while(!Q.empty()){
int size = Q.size();
level++;//到了下一层
while(size--){
int cur = Q.front();
Q.pop();
for(int s : squares){
int next = cur - s;
if(next < 0){
break;//由于squares是升序的,故当一个差为负时没必要继续判断之后的了
} else if(next == 0){
return level; //到达了0节点直接返回最短路径长度
} else if(!visit[next]){
visit[next] = true;
Q.push(next);
}
}
}
}
return n;
}
};
二、深度优先搜索DFS
1. 岛屿的最大面积 难度:medium Leetcode题号:695
题目链接:https://leetcode-cn.com/problems/max-area-of-island/description/
题目描述:
思路:典型的dfs,对每个坐标的上下左右进行搜索,取找到的1的个数的最大值,注意当搜索到一个值为1的位置后,应将其值置为0(类似于将标记位置为1),然后再扩展进一步搜索
C++代码如下:
class Solution {
public:
int direction[4][2] = {{0,1}, {0,-1}, {-1,0}, {1,0}};
int maxAreaOfIsland(vector<vector<int>>& grid) {
if(grid.size()==0 || grid[0].size()==0){
return 0;
}
int m = grid.size();
int n = grid[0].size();
int maxArea = 0;
for(int i = 0 ; i < m ; i++){
for(int j = 0; j < n; j++){
maxArea = max(maxArea, dfs(grid, i, j));
}
}
return maxArea;
}
int dfs(vector<vector<int>>& grid, int x, int y){
if(x < 0 || x >= grid.size() || y < 0 || y >= grid[0].size() || grid[x][y] == 0){
return 0;
}
grid[x][y] = 0;
int area = 1;
for(int i = 0 ; i < 4 ; i++){
int next_x = x + direction[i][0];
int next_y = y + direction[i][1];
area += dfs(grid, next_x, next_y);
}
return area;
}
};
2. 岛屿数量(求连通分支数) 难度:medium Leetcode题号:200
题目链接:https://leetcode-cn.com/problems/number-of-islands/description/
题目描述:
显然本题目是求解矩阵中的连通分支数目,思路如下:
(1)当找到一个值为1的元素时,首先将连通分支数加1,然后将该元素置为0,然后对其上下左右四个元素应用dfs,执行相同的过程,若元素本来就是0,则可以直接返回;这样运行一次完整的dfs就会将一个连通分支中的所有元素变为0,
(2)以后每遍历到一个值为1的元素,则表明有新的连通分支出现了,重复(1)过程即可
C++代码如下:
class Solution {
public:
int direction[4][2] = { {0,1}, {0,-1}, {1,0}, {-1,0}};
int m , n;
int numIslands(vector<vector<char>>& grid) {
if(grid.size() == 0 || grid[0].size() == 0){
return 0;
}
m = grid.size();
n = grid[0].size();
int ans = 0;
for(int i = 0 ; i < m ; i++){
for(int j = 0; j < n ; j++){
if(grid[i][j] == '1'){
ans++;
dfs(grid, i , j);
}
}
}
return ans;
}
void dfs(vector<vector<char>>& grid, int x, int y){
if(x < 0 || x >= m || y < 0 || y >=n || grid[x][y] == '0'){
return ;
}
grid[x][y] = '0';
for(int i = 0 ; i < 4; i++){
int next_x = x + direction[i][0];
int next_y = y + direction[i][1];
dfs(grid, next_x, next_y);
}
}
};
3. 朋友圈 难度:medium Leetcode题号:547
题目链接:https://leetcode-cn.com/problems/friend-circles/description/
题目描述:
dfs代码:
class Solution {
public:
int n;
int findCircleNum(vector<vector<int>>& M) {
if(M.size() == 0 || M[0].size() == 0){
return 0;
}
n = M.size();
bool visit[n];
memset(visit, false, sizeof(visit));
int ans = 0;
for(int i = 0 ; i < n ; i++){
if(!visit[i]){
ans++;
dfs(M, visit, i);
}
}
return ans;
}
void dfs(vector<vector<int>>& M, bool visit[], int x){
if(visit[x]){
return;
}
visit[x] = true;
for(int i = 0; i < M.size(); i++){
if(!visit[i] && M[x][i] == 1){
dfs(M, visit, i);
}
}
}
};
4. 被围绕的区域 难度:medium Leetcode题号:130
题目链接:
https://leetcode-cn.com/problems/surrounded-regions/submissions/
题目描述:
思路:
由题目解释可知,在矩阵边界上的'O'以及与其在一个连通分支(即与其直接或间接可达)的'O'不会被填充,那么可以分两步进行:
(1)对边界上所有'O'进行一遍dfs,将它所在的连通分支所有'O'元素都用'-'替代('-'可以是除了'X'和'O'之外的任意一种字符)
(2)(1)过程执行完之后,所有应被填充的元素值没有变,仍是'O',故应将填充为为'X',而'-'是不应被填充的元素,故应将其重置为'O'
C++ dfs代码如下:
class Solution {
public:
int direction[4][2] = { {1,0},{-1,0},{0,1},{0,-1}};
int m, n;
void solve(vector<vector<char>>& board) {
if(board.size() == 0 || board[0].size() == 0){
return ;
}
m = board.size();
n = board[0].size();
for(int i = 0 ; i < m; i++){
for(int j = 0 ; j < n; j++){
if(i == 0 || i == m - 1 || j == 0 || j == n - 1 ){
if(board[i][j] != 'O'){
continue;
}
dfs(board, i , j);
}
}
}
for(int i = 0 ; i < m ; i++){
for(int j = 0 ; j < n; j++){
if(board[i][j] == 'O'){
board[i][j] = 'X';
} else if(board[i][j] == '-'){
board[i][j] = 'O';
}
}
}
}
void dfs(vector<vector<char>>& board, int x, int y){
if(x < 0 || x >= m || y < 0 || y>=n || board[x][y] != 'O'){
return ;
}
board[x][y] = '-';
for(int i = 0; i < 4; i++){
int next_x = x + direction[i][0];
int next_y = y + direction[i][1];
dfs(board, next_x, next_y);
}
}
};
5. 太平洋大西洋水流问题 难度:medium Leetcode题号:417
题目链接:
https://leetcode-cn.com/problems/pacific-atlantic-water-flow/
题目描述:
思路:
与第上题被填充的区域类似,该题同样应考虑从边界逆流而上去寻找连通分支,只不过这里的联通分支要求每个坐标的高度要非递减(即下一个坐标高度要大于等于当前坐标高度),另外,需要设置两个坐标的标记数组(这里使用vector),分别记录能否从太平洋和大西洋到达该坐标,然后分别从太平洋和大西洋逆流而上进行dfs,最后遍历所有坐标的两个标记值,若都为true则表示从两个大洋都能到达该坐标(即从该坐标能到达两个大洋)
C++ dfs代码如下:
class Solution {
public:
int m,n;
int direction[4][2]= {{0,1}, {0,-1}, {1,0}, {-1,0}}; //四个水流方向
vector<vector<int>> pacificAtlantic(vector<vector<int>>& matrix) {
vector<vector<int>> ans;//结果集合
if(matrix.size() == 0 || matrix[0].size() == 0){
return ans;
}
m = matrix.size();
n = matrix[0].size();
vector<vector<bool>> canReachP(m, vector<bool>(n,false));//能从太平洋回流到的标志
vector<vector<bool>> canReachA(m, vector<bool>(n,false));//能从大西洋回流到的标志
for(int i = 0; i < m; i++){
for(int j = 0; j < n; j++){
if(i == 0 || j == 0){ //从与太平洋连接的坐标开始逆流而上寻找
dfs(matrix, canReachP, i, j);
}
if(i == m-1 || j == n-1){ //从与大西洋连接的坐标开始逆流而上寻找
dfs(matrix, canReachA, i, j);
}
}
}
for(int i = 0 ; i < m; i++){
for(int j = 0; j < n; j++){
if(canReachA[i][j] && canReachP[i][j]){ //若两个大洋都能逆流到该坐标
ans.push_back({i,j});
}
}
}
return ans;
}
bool judge(int x,int y){ //判断坐标是否在合法范围内
return x>=0 && x<m && y>=0 && y<n;
}
void dfs(vector<vector<int>>& matrix, vector<vector<bool>>& canReach, int x, int y){
if(canReach[x][y]){
return ;
}
canReach[x][y] = true;
for(int i = 0 ; i < 4; i++){
int next_x = x + direction[i][0];
int next_y = y + direction[i][1];
//由于是逆流而上,所以要找高度比当前坐标大或相等的
if(judge(next_x, next_y) && matrix[next_x][next_y] >= matrix[x][y]){
dfs(matrix, canReach, next_x, next_y);
}
}
}
};