C/C++优先搜索


算法解释

深度优先搜索广度优先搜索是两种常见的优先搜索算法,广泛应用于图和树等结构中搜索。

深度优先搜索

深度优先搜索(depth-first seach,DFS)在搜索到一个新的节点时,立即对该新节点进行遍历;因此遍历需要用先入后出的栈来实现,也可以通过与栈等价的递归来实现。对于树结构而言,由于总是对新节点调用遍历,因此看起来是向着“深”的方向前进。

深度优先搜索也可以用来检测环路:记录每个遍历过的节点的父节点,若一个节点被再次遍历且父节点不同,则说明有环。我们也可以用之后会讲到的拓扑排序判断是否有环路,若最后存在入度不为零的点,则说明有环。
有时我们可能会需要对已经搜索过的节点进行标记,以防止在遍历时重复搜索某个节点,这种做法叫做状态记录或记忆化(memoization)。

695.岛屿的最大面积(medium)

题目描述

给定一个二维的 0-1 矩阵,其中 0 表示海洋,1 表示陆地。单独的或相邻的陆地可以形成岛屿,每个格子只与其上下左右四个格子相邻。求最大的岛屿面积

输入输出样例

在这里插入图片描述

输入:grid = [[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]]
输出:6

解释:答案不应该是 11 ,因为岛屿只能包含水平或垂直这四个方向上的 1 。

题解

一般来说,深度优先搜索类型的题可以分为主函数和辅函数,主函数用于遍历所有的搜索位置,判断是否可以开始搜索,如果可以即在辅函数进行搜索。辅函数则负责深度优先搜索的递归调用。当然,我们也可以使用栈(stack)实现深度优先搜索,但因为栈与递归的调用原理相同,而递归相对便于实现,因此推荐使用递归式写法,同时也方便进行回溯(见下节)。不过在实际工程上,直接使用栈可能才是最好的选择,一是因为便于理解,二是更不易出现递归栈满的情况。

代码

class Solution {
public:
    int dfs(vector<vector<int>>& grid,int r,int c){
        if(grid[r][c]==0) return 0;
        grid[r][c]=0;
        int area=1;
        if(r+1>=0&&r+1<grid.size()&&c>=0&&c<grid[0].size()){
            area+=dfs(grid,r+1,c);
        }
        if(r-1>=0&&r-1<grid.size()&&c>=0&&c<grid[0].size()){
            area+=dfs(grid,r-1,c);
        }
        if(r>=0&&r<grid.size()&&c+1>=0&&c+1<grid[0].size()){
            area+=dfs(grid,r,c+1);
        }
        if(r>=0&&r<grid.size()&&c-1>=0&&c-1<grid[0].size()){
            area+=dfs(grid,r,c-1);
        }
        return area;
    }
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        if(grid.empty()||grid[0].empty()) return 0;
        int maxarea=0;
        for(int i=0;i<grid.size();i++){
            for(int j=0;j<grid[i].size();j++){
                maxarea=max(maxarea,dfs(grid,i,j));
            }
        }
        return maxarea;
    }
};

因为没有用方向数组显得代码比较乱,下面介绍第二种的递归写法

class Solution {
public:
    int dfs(vector<vector<int>>& grid,int r,int c){
        if(r<0||r>=grid.size()||c<0||c>=grid[0].size()||grid[r][c]==0) return 0;
        grid[r][c]=0;
        return 1+dfs(grid,r+1,c)+dfs(grid,r-1,c)+dfs(grid,r,c+1)+dfs(grid,r,c-1);
    }
    int maxAreaOfIsland(vector<vector<int>>& grid) {
        if(grid.empty()||grid[0].empty()) return 0;
        int maxarea=0;
        for(int i=0;i<grid.size();i++){
            for(int j=0;j<grid[i].size();j++){
                maxarea=max(maxarea,dfs(grid,i,j));
            }
        }
        return maxarea;
    }
};

547.省份数量(Medium)

题目描述

有 n 个城市,其中一些彼此相连,另一些没有相连。如果城市 a 与城市 b 直接相连,且城市 b 与城市 c 直接相连,那么城市 a 与城市 c 间接相连。

省份 是一组直接或间接相连的城市,组内不含其他没有相连的城市。

输入输出描述

输入:isConnected = [[1,1,0],[1,1,0],[0,0,1]]
输出:2

题解

这题需要一个额外数组来记录遍历状态

代码

class Solution {
public:
    void dfs(vector<vector<int>>& isConnected,vector<bool>& isTrave,int i){
        if(isTrave[i]) return;
        isTrave[i]=true;
        for(int j=0;j<isConnected.size();j++){
            if(isConnected[i][j]==1&&!isTrave[j]){
                dfs(isConnected,isTrave,j);
            }
        }
    }
    int findCircleNum(vector<vector<int>>& isConnected) {
        vector<bool> isTrave(isConnected.size(),false);
        int ret=0;
        for(int i=0;i<isConnected.size();i++){
            if(!isTrave[i]){
                dfs(isConnected,isTrave,i);
                ret++;
            }
        }
        return ret;
    }
};

417.太平洋大西洋水流问题

题目描述

给定一个二维的非负整数矩阵,每个位置的值表示海拔高度。假设左边和上边是太平洋,右边和下边是大西洋,求从哪些位置向下流水,可以流到太平洋和大西洋。水只能从海拔高的位置流到海拔低或相同的位置。

输入输出样例

给定下面的 5x5 矩阵:

  太平洋 ~   ~   ~   ~   ~ 
       ~  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  *
          *   *   *   *   * 大西洋

返回:

[[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]] (上图中带括号的单元).

题解

虽然题目要求的是满足向下流能到达两个大洋的位置,如果我们对所有的位置进行搜索,那么在不剪枝的情况下复杂度会很高。因此我们可以反过来想,从两个大洋开始向上流,这样我们只需要对矩形四条边进行搜索。搜索完成后,只需遍历一遍矩阵,满足条件的位置即为两个大洋向上流都能到达的位置。

代码

class Solution {
public:
    vector<int> direction{-1,0,1,0,-1};
    void dfs(vector<vector<int>>& heights,vector<vector<bool>>& trace,int r,int c){
        if(trace[r][c]) return;
        trace[r][c]=true;
        int x,y;
        for(int i=0;i<4;i++){
            x=r+direction[i],y=c+direction[i+1];
            if(x>=0&&x<heights.size()&&y>=0&&y<heights[0].size()&&heights[r][c]<=heights[x][y]){
                dfs(heights,trace,x,y);
            }
        }
    }
    vector<vector<int>> pacificAtlantic(vector<vector<int>>& heights) {
        if(heights.empty()||heights[0].empty()) return {};
        vector<vector<int>> res;
        int m=heights.size(),n=heights[0].size();

        vector<vector<bool>> can_trace_p(m,vector<bool>(n,false));
        vector<vector<bool>> can_trace_a(m,vector<bool>(n,false));

        for(int i=0;i<m;i++){
            dfs(heights,can_trace_p,i,0);
            dfs(heights,can_trace_a,i,n-1);
        }
        for(int i=0;i<n;i++){
            dfs(heights,can_trace_p,0,i);
            dfs(heights,can_trace_a,m-1,i);
        }
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                if(can_trace_a[i][j]&&can_trace_p[i][j]) res.push_back({i,j});
            }
        }
        return res;
    }
};

130.被围绕的区域

题目描述

给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

输入输出样例

在这里插入图片描述

输入:board = [["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'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。

题解

任何边界上的 O 都不会被填充为 X。 我们可以想到,所有的不被包围的 O 都直接或间接与边界上的 O 相连。

代码

class Solution {
public:
    void dfs(vector<vector<char>>& board,int r,int c){
        if(r<0||r>=board.size()||c<0||c>=board[0].size()||board[r][c]!='O') return;
        board[r][c]='A';
        dfs(board,r+1,c);
        dfs(board,r-1,c);
        dfs(board,r,c+1);
        dfs(board,r,c-1);
    }
    void solve(vector<vector<char>>& board) {
    	//通过dfs,将与边界相连或者在边界上的O置为A
        for(int i=0;i<board.size();i++){
            dfs(board,i,0);
            dfs(board,i,board[0].size()-1);
        }
        for(int j=1;j<board[0].size()-1;j++){
            dfs(board,0,j);
            dfs(board,board.size()-1,j);
        }
        //遍历,完成题目要求
        for(int i=0;i<board.size();i++){
            for(int j=0;j<board[0].size();j++){
                if(board[i][j]=='A'){
                    board[i][j]='O';
                }else{
                    board[i][j]='X';
                }
            }
        }
    }
};

257.二叉树的所有路径

题目描述

给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。

叶子节点 是指没有子节点的节点。

输入输出样例

在这里插入图片描述

输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]

题解

直接使用深度优先搜索,从root开始遍历。当root为nullptr时,就结束。

  • 如果当前结点不是叶子结点,继续遍历左右结点。
  • 如果是叶子结点,则将该路径加入到答案中。

代码

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<string> res;
    void dfs(TreeNode* root,string s){
        if(root==nullptr) return;
        s+=to_string(root->val);
        if(root->left==nullptr&&root->right==nullptr) {
            res.push_back(s);
        }
        else{
            s+="->";
            dfs(root->left,s);
            dfs(root->right,s);
        }

    }
    vector<string> binaryTreePaths(TreeNode* root) {
        string s;
        dfs(root,s);
        return res;
    }
};

回溯法

具体详见另一笔记(正在完成,稍后更新):

C/C++回溯法

广度优先搜索

广度优先搜索(breadth-first search,BFS)不同与深度优先搜索,它是一层层进行遍历的,因此需要用先入先出的队列而非先入后出的栈进行遍历。由于是按层次进行遍历,广度优先搜索时按照“广”的方向进行遍历的,也常常用来处理最短路径等问题。

934.最短的桥

题目描述

给定一个二维 0-1 矩阵,其中 1 表示陆地,0 表示海洋,每个位置与上下左右相连。已知矩阵中有且只有两个岛屿,求最少要填海造陆多少个位置才可以将两个岛屿相连。

输入输出样例

输入:A = [[0,1],[1,0]]
输出:1

题解

这题我认为是比较难的,需要先确定两个岛屿,然后将其中一座岛屿不断延申,直到到达另一座的岛屿。

在寻找岛屿时,我们使用深度优先搜索。向外延伸时,使用广度优先搜索。

其中,最小步数就是广度优先搜索的层数

详解看代码备注

代码

class Solution {
public:
    vector<int> direction{-1,0,1,0,-1};

    int shortestBridge(vector<vector<int>>& grid) {
        int m=grid.size(),n=grid[0].size();
        queue<pair<int,int>> points;
        //dfs寻找岛屿A,并把1全部赋值为2;设置标志位,找到第一个1后立即停止
        bool flag=false; 
        for(int i=0;i<m;i++){
            if(flag) break;
            for(int j=0;j<n;j++){
                if(grid[i][j]==1){
                    dfs(points,grid,i,j);
                    flag=true;
                    break;
                }
            }
        }
        //通过广度优先搜索找到岛屿B,并把寻找过程中0变为2,延伸岛屿A
        //延伸一圈,步数+1,直到与另一座岛屿B接触
        int x,y;
        int level=0;
        while(!points.empty()){
            ++level;
            int n_points=points.size();
            while(n_points--){
                //取出一个岛屿A周围的0,并遍历。
                auto [r,c]=points.front();
                points.pop();
                //如果遇到1,则说明两岛屿相遇,如果没有,则继续延伸
                for(int k=0;k<4;k++){
                    x=r+direction[k],y=c+direction[k+1];
                    //如果没有越界
                    if(x>=0&&y>=0&&x<m&&y<n){
                        if(grid[x][y]==2) continue;
                        if(grid[x][y]==1) return level;
                        points.push({x,y});
                        grid[x][y]=2;
                    }                    
                }
            }
        }
        return 0;
    }

    //深度优先搜索,将一座岛屿A变成2
    void dfs(queue<pair<int,int>>& points,vector<vector<int>>& grid,int i,int j){
        int m=grid.size(),n=grid[0].size();
        //如果越界或者已经遍历到的,直接返回
        if(i<0||j<0||i>=m||j>=n||grid[i][j]==2) return;
        //将这座岛屿A周围为0的区域存储起来,并返回
        if(grid[i][j]==0){
            points.push({i,j});
            return;
        }        
        //将岛屿A设为2,并遍历四周
        grid[i][j]=2;
        dfs(points,grid,i+1,j);
        dfs(points,grid,i-1,j);
        dfs(points,grid,i,j+1);
        dfs(points,grid,i,j-1);
    }
};

126.单词接龙Ⅱ(Hard)

题目描述

给定一个起始字符串和一个终止字符串,以及一个单词表,求是否可以将起始字符串每次改一个字符,直到改成终止字符串,且所有中间的修改过程表示的字符串都可以在单词表里找到。若存在,输出需要修改次数最少的所有更改方式。

输入输出样例

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:[["hit","hot","dot","dog","cog"],["hit","hot","lot","log","cog"]]
解释:存在 2 种最短的转换序列:
"hit" -> "hot" -> "dot" -> "dog" -> "cog"
"hit" -> "hot" -> "lot" -> "log" -> "cog"

题解

我们可以把起始字符串、终止字符串、以及单词表里所有的字符串想象成节点。若两个字符串只有一个字符不同,那么它们相连。因为题目需要输出修改次数最少的所有修改方式,因此我们可以使用广度优先搜索,求得起始节点到终止节点的最短距离。

在搜索结束后,我们还需要通过回溯法来重建所有可能的路径。

代码

在这里插入代码片
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值