回溯dfs和分支限界bfs

一:拓扑排序

207. 课程表

这道题说白了就是在有向图中找环

拓扑排序实际上应用的是贪心算法

贪心算法简而言之:每一步最优,全局就最优。

  • 每一次都从图中删除没有前驱的顶点,这里并不需要真正的删除操作,通过设置入度数组。

  • 每一轮都输出入度为 0的结点,并移除它,同时修改它指向的结点的入度(−1-即可)

    —依次得到的结点序列就是拓扑排序的结点序列

    如果图中还有结点没有被移除,则说明“不能完成所有课程的学习”。

拓扑排序顺序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后的排序结果

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

拓扑排序不能输出所有结点就说明原来的有向图有环

有环必存在一条边指向,不满足入度为0,也就不会输出

在这里插入图片描述

本题解法:

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
         vector<vector<int>> graph(numCourses,vector<int>());
         //建立入度数组
         vector<int> indegree(numCourses);
         //创建图:[u,v]---v->u
         for(auto val:prerequisites){
            int u=val[0];
            int v=val[1];
            graph[v].push_back(u);
            indegree[u]++;//更新入度
         }
         queue<int> que;//装顶点,也就是下标
         //入读为0的节点入队
         for(int i=0;i<numCourses;i++){
             if(indegree[i]==0) que.push(i);
         }
         while(!que.empty()){
            auto x=que.front();
            que.pop();
            //说明一门课程满足结果
            numCourses--;
            for(auto val:graph[x]){
                indegree[val]--;//移除边
                //把入度为0的结点入队
                if(indegree[val]==0){
                    que.push(val);
                }
            }
         }
         return numCourses==0;
    }
};

也可以用map<顶点,顶点的出边指向点集合>来创建图

class Solution {
public:
    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
         //set(顶点)=顶点的出边
         unordered_map<int,set<int>> mp;
         //建立入度数组
         vector<int> indegree(numCourses);
         //创建图:[u,v]---v->u
         for(auto val:prerequisites){
            int u=val[0];
            int v=val[1];
            mp[v].insert(u);//这里是set要用insert
            indegree[u]++;//更新入度
         }
         queue<int> que;//装顶点,也就是下标
         //入读为0的节点入队
         for(int i=0;i<numCourses;i++){
             if(indegree[i]==0) que.push(i);
         }
         int col=0;
         while(!que.empty()){
            auto x=que.front();
            que.pop();
            //说明一门课程满足结果
            col++;
            for(auto val:mp[x]){
                indegree[val]--;//移除边
                //把入度为0的结点入队
                if(indegree[val]==0){
                    que.push(val);
                }
            }
         }
         return col==numCourses;
    }
};

矩阵最长路径

329. 矩阵中的最长递增路径

这道题的难点在于:把矩阵抽象成图,然后对图进行拓扑排序,就可以得到所求

  • 抽象成图

m×n的矩阵中每一个值作为图的顶点,顶点数设置为i×n+j,那么对于每个点进行上下左右遍历,只要值大于它,也就是满足了单调递增的条件,就指向它。

比如:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

可以抽象成:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

  • 对创建好的图进行拓扑排序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

解答:

class Solution {
public:
    int longestIncreasingPath(vector<vector<int>>& matrix) {
        /*创建图*/
        this->m=matrix.size();
        this->n=matrix[0].size();
        //注意:结点数是m*n
        this->graph=vector<vector<int>>(m*n,vector<int>());
        //遍历结点加边顺便统计入度
        indegree=vector<int>(m*n,0);
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                addEdge(matrix,i,j);
            }
        }
        /*拓扑排序*/
        queue<int> que;
        vector<int> dist(m*n,1);//长度包含自己,最小1
        //结点为0入队
        for(int i=0;i<m*n;i++){
            if(indegree[i]==0) que.push(i);
        }
        //去掉该节点的出边,选择其他的入度为0结点
        while(!que.empty()){
            int tmp=que.front();
            que.pop();
            for(auto& val:graph[tmp]){
                indegree[val]--;
                //指向结点dist更新
                dist[val]=max(dist[val],dist[tmp]+1);
                if(indegree[val]==0){
                    que.push(val);
                }
            }
        }
        /*遍历dist,取最大*/
        int ans=0;
        for(int i=0;i<m*n;i++){
            ans=max(ans,dist[i]);
        }
        return ans;
    }
private:
   int m,n;
   vector<int> indegree;
   vector<vector<int>> graph;
   int getNode(int x,int y){ return x*n+y;};
   int d[4][2]={{1,0},{-1,0},{0,1},{0,-1}};
   bool isInArea(int x,int y){
        return x>=0 && y>=0 && x<m && y<n;
   }
   void addEdge(vector<vector<int>>& matrix,int x,int y){
        for(int i=0;i<4;i++){
            int nx=x+d[i][0];
            int ny=y+d[i][1];
            if(isInArea(nx,ny)&& matrix[x][y]<matrix[nx][ny]){
                int u=getNode(x,y);
                int v=getNode(nx,ny);
                graph[u].push_back(v);
                indegree[v]++;//入度++
            }
        }
   } 
};

二:求树的每一层的最大值

515. 在每个树行中找最大值

很明显要分清每一层的结点,然后比较同层的值

每次循环的队列值就是当前层结点

   vector<int> largestValues(TreeNode* root) {
        vector<int> res;
        if(root==NULL) return res;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()){
            int sz=que.size();
            int n=INT_MIN;
            while(sz-->0){
                auto tmp=que.front();
                que.pop();
                n=max(n,tmp->val);
                if(tmp->left) que.push(tmp->left);
                if(tmp->right) que.push(tmp->right);
            }
            res.push_back(n);
        }
        return res;
    }

也可以用深度优先遍历来做,注意层高

class Solution {
public:
    vector<int> largestValues(TreeNode* root) {
        //if(root==NULL) return res;
        dfs(root,0);//注意从0开始
        return res;
    }
private:
    vector<int> res;
    void dfs(TreeNode* root,int h){
        if(root==NULL) return;
        //合法性检查:res[]要符合
        if(h==res.size()){
            res.push_back(INT_MIN);
        }
        //存放结果
        res[h]=max(res[h],root->val);
        //递归
        dfs(root->left,h+1);
        dfs(root->right,h+1);
    }
};

三:搜索问题知识点

搜索是一种枚举的算法,所有不能多项式时间求解的NP问题都需要靠搜索求解

定义搜索框架会极大地帮助学习动态规划和图论算法

状态空间图

状态:对问题在某一时刻的进展情况的数学描述,从一种状态转移到另一种(或几种)状态的操作叫:状态转移

状态空间就是在搜索过程中所有状态的集合

本质上,状态就是程序中程序所维护的所有动态数据结构的集合

注意状态只关注动态的数据

如果其他信息可以通过其他数据决定,那么只关注最根本的信息

如果把状态视为一个点,从一个状态可以到达另一个状态就连一条边,那么整个状态空间就是一张有向图

  • 节点是搜索问题中定义的状态
  • 边代表动作所导致的转换

----对状态空间的搜索就相当于对状态空间图进行遍历

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

搜索题解题步骤:

先提取动态变量,找出最基本的变量(不会被其他变量所确定),然后确定遍历顺序

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

子集和排列的状态空间

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

搜索其实就是遍历整个状态空间图来寻找答案的一类算法,可以分为深度优先遍历和广度优先遍历。

一般来说:一种状态只需要遍历一次,所以如果状态空间图不是树,就需要判重,也就是记忆化。

四:搜索问题力扣题

电话号码组合

17. 电话号码的字母组合

​ 本题属于子集类型的回溯,每个字母有若干个字符,最后只从其中选一个。所以递归参数需要数字i,表示当前访问的第i个数字。

class Solution {
public:
    const string letterMap[9]{//letter[1]对应2
        "",
        "abc",
        "def",
        "ghi",
        "jkl",
        "mno",
        "pqrs",
        "tuv",
        "wxyz"
    };
    vector<string> letterCombinations(string digits) {
        this->digits=digits;
        if(digits=="") return res;
        dfs(0);
        return res;
    }
private:
    string digits;
    string ans;
    vector<string> res;
    void dfs(int index){//第index数字
         if(index==digits.size()){
            res.push_back(ans);
            return;//别忘了满足题目要求后需要return
         }
         int x=digits[index]-'0';
         string tmp=letterMap[x-1];
         for(int i=0;i<tmp.size();i++){
            ans+=tmp[i];
            dfs(index+1);
            ans.pop_back();
         }
    }
};

当然也可以用map存储,更直观一些

class Solution {
public:
    vector<string> letterCombinations(string digits) {
        this->digits=digits;
        //定义map
        mp['2']="abc";        
        mp['3']="def";
        mp['4']="ghi";
        mp['5']="jkl";
        mp['6']="mno";
        mp['7']="pqrs";
        mp['8']="tuv";
        mp['9']="wxyz";     
        if(digits=="") return res;
        dfs(0);
        return res;
    }
private:
    string digits;
    string ans;
    unordered_map<char,string> mp;
    vector<string> res;
    void dfs(int index){//第index数字
         if(index==digits.size()){
            res.push_back(ans);
            return;//别忘了满足题目要求后需要return
         }
         string tmp=mp[digits[index]];//mp[digits对应的数字]
         for(int i=0;i<tmp.size();i++){
            ans+=tmp[i];
            dfs(index+1);
            ans.pop_back();
         }
    }
};

n皇后

51. N 皇后

思路:其实就是全排列的基础上加上不能选上一个对应的两个对角线位置

同一个对角线的特点,要么和为定值,要么差为定值

n×n的一面对角线个数:2×n-1

和为定值

![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=C%3A%5CUsers%5C24732%5CPictures%5C%E4%B8%89%E6%98%9F%E5%A4%9A%E5%B1%8F%E8%81%94%E5%8A%A8%5C1%20(2&pos_id=img-OOHPmEc8-1711736681207).png

差为定值

用数组存储注意有负数,需要加上n-1

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        this->n=n;
        this->visit=vector<bool>(n,false);
        this->dig_sum=vector<bool>(n*2-1,false);
        this->dig_minus=vector<bool>(n*2-1,false);
        dfs(0);
        //把数字对应成棋盘
        //比如1302对应:.Q..|...Q|Q...|..Q.
        vector<vector<string>> res;
        for(auto val:vec){
            //一个数字排列
            vector<string> tmp;
            for(auto x:val){
                string ss(n,'.');
                ss[x]='Q';
                tmp.push_back(ss);
            }
            res.push_back(tmp);
        }
        return res;
    }
private:
   //先写出不同行不同列不同对角线的全排列
   int n;
   vector<vector<int>> vec;
   vector<int> ans;
   vector<bool> visit;
   //两个对角线---撇是下标和相同,捺是下标差相同
   //注意:n×n的棋盘有:2*n-1个对角线
   vector<bool> dig_sum;
   //注意差下标有负数,所以加上n-1 (0,n-1)
   vector<bool> dig_minus;
   void dfs(int index){
        if(ans.size()==n){
            vec.push_back(ans);
            return;
        }
        for(int i=0;i<n;i++){
            if(!visit[i]&&!dig_sum[i+index]&&!dig_minus[n-1+i-index]){
                visit[i]=true;
                dig_minus[i-index+n-1]=true;
                dig_sum[i+index]=true;
                ans.push_back(i);
                dfs(index+1);
                ans.pop_back();
                dig_minus[i-index+n-1]=false;
                dig_sum[i+index]=false;
                visit[i]=false;
            }
        }
   }
};

当然也可以用map,map不必考虑下标为负数情况

      unordered_map<int,bool> dig_plus;
      unordered_map<int,bool> dig_minus;
        //直接i+index和i-index(或index-i)
        for(int i=0;i<n;i++){
            if(!visit[i]&&!dig_plus[i+index]&&!dig_minus[i-index]){
                visit[i]=true;
                dig_minus[i-index]=true;
                dig_plus[i+index]=true;
                ans.push_back(i);
                dfs(index+1);
                ans.pop_back();
                dig_minus[i-index]=false;
                dig_plus[i+index]=false;
                visit[i]=false;
            }
        }
 

这里也可以每次得到一个vector后直接存储vector

class Solution {
public:
    vector<vector<string>> solveNQueens(int n) {
        this->n=n;
        this->visit=vector<bool>(n,false);
        dfs(0);
        return res;
    }
private:
   //先写出不同行不同列不同对角线的全排列
   int n;
   vector<vector<string>> res;
   vector<int> ans;
   vector<bool> visit;
   unordered_map<int,bool> dig_plus;
   unordered_map<int,bool> dig_minus;
   vector<string> qipan(vector<int>& ans){
       //[1,2,3,4]
       vector<string> board(n,string(n,'.'));
       for(int i=0;i<n;i++){
           board[i][ans[i]]='Q';
       }
       return board;
   }
   void dfs(int index){
        if(ans.size()==n){
            //在这里把一个ans结果变成棋盘
            res.push_back(qipan(ans));
            return;
        }
        for(int i=0;i<n;i++){
            if(!visit[i]&&!dig_plus[index-i]&&!dig_minus[i+index]){
                visit[i]=true;
                dig_minus[i+index]=true;
                dig_plus[index-i]=true;
                ans.push_back(i);
                dfs(index+1);
                ans.pop_back();
                dig_minus[i+index]=false;
                dig_plus[index-i]=false;
                visit[i]=false;
            }
        }
   }
};

地图类搜索----岛屿数量

200. 岛屿数量

每次dfs都可以找到一个连通分支,因为dfs的递归结果就是连通树或者连通图

本题思路:

对整张图进行dfs搜索,dfs搜索次数就是符合题目要求的数量

dfs回溯时注意:

这里每一个点(x,y两个方向),都可以向上左右移动,这里边界(四周)有移动越界问题

所以可以继续进行dfs的条件是:

  • (x,y)运动后的坐标不越界
  • 移动后的(x,y)对应的结点没有访问过
  • 移动后的(x,y)grid值为1不是0

而且条件之间有依赖,是否越界这个条件必须首先判断

  • 也可以理解成:访问数组必须先判断下标合法性
dfs
class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        this->m=grid.size();
        if(m==0) return res;
        this->n=grid[0].size();
        this->grid=grid;
        this->visit=vector<vector<bool>>(m,vector<bool>(n,false));
        //遍历整个地图
        for(int i=0;i<m;i++){
            for(int j=0;j<n;j++){
                //是陆地而且这个陆地没有被访问
                if(grid[i][j]=='1'&&!visit[i][j]){
                    res++;//联通分量+1
                    dfs(i,j);
                }
            }
        }
        return res;
    }
private:
    int res=0;
    vector<vector<char>> grid;
    vector<vector<bool>> visit;
    int m,n;//m是grid数量是宽,n是grid每一个vector的长
    void dfs(int x,int y){
        //越界return
        if(x<0 || y<0 || x==m || y==n){
             return;
        }
        //不是陆地或者访问过 return
        //必须先检查越界情况
        if(grid[x][y]=='0' || visit[x][y]) return;
        //标记已经访问
        visit[x][y]=true;
        //四个方向搜索
        dfs(x+1,y);
        dfs(x-1,y);
        dfs(x,y+1);
        dfs(x,y-1);
    }
};

当然可以利用for循环和dx,dy来简化搜索,还可以把越界检查写成函数

int dx[4]={1,-1,0,0};
    int dy[4]={0,0,1,-1};
    bool isInArea(int x,int y){
        return x>=0&&y>=0&&x<m&&y<n;
    }
    void dfs(int x,int y){
        //越界return
        if(!isInArea(x,y)){
             return;}
        if(grid[x][y]=='0' || visit[x][y]) return;
        //标记已经访问
        visit[x][y]=true;
        //四个方向搜索
        for(int i=0;i<4;i++){
            x+=dx[i];
            y+=dy[i];
            dfs(x,y);
            //回溯
            x-=dx[i];
            y-=dy[i];
        }
    }

当然可以不符合条件递归,也可以符合条件后再dfs,但是必须先判断越界然后判断陆地和是否访问过

情况1:

  int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    bool isInArea(int x, int y) { return x >= 0 && y >= 0 && x < m && y < n; }
    void dfs(int x, int y) {
        if (grid[x][y] == '0' || visit[x][y])
            return;
        // 标记已经访问
        visit[x][y] = true;
        // 四个方向搜索
        for (int i = 0; i < 4; i++) {
            x += dx[i];
            y += dy[i];
            if (isInArea(x, y))
                dfs(x, y);
            // 回溯
            x -= dx[i];
            y -= dy[i];
        }
    }

情况2:定义新变量不用回溯

 int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    bool isInArea(int x, int y) { return x >= 0 && y >= 0 && x < m && y < n; }
    void dfs(int x, int y) {
        // 标记已经访问
        visit[x][y] = true;
        // 四个方向搜索
        for (int i = 0; i < 4; i++) {
            int newx = x + dx[i];
            int newy = y + dy[i];
            if (isInArea(newx, newy) && grid[newx][newy] == '1' &&
                !visit[newx][newy])
                dfs(newx, newy);
        }
    }
bfs

和dfs一样,只不过bfs在搜索时侯,会把下一个上下左右可访问的结点入队

 int numIslands(vector<vector<char>>& grid) {
        int res=0;
        int n=grid.size();
        if(n==0) return 0;
        int m=grid[0].size();
        vector<vector<bool>> visit(n,vector<bool>(m,false));
        //遍历整个grid
        for(int i=0;i<n;i++){
            for(int j=0;j<m;j++){
                //遇到0,跳出本次循环
                if(grid[i][j]=='0'||visit[i][j]) continue;
                //更新结果
                res++;
                visit[i][j]=true;
                //队列存放坐标值i和j
                queue<pair<int,int>> que;
                que.push({i,j});
                while(!que.empty()){
                    auto [x,y]=que.front();
                    que.pop();
                    //周围四个入队,注意合法性检查
                    if(x+1<n && grid[x+1][y]=='1'&&!visit[x+1][y]){
                        que.push({x+1,y});
                        visit[x+1][y]=true;
                    }
                    if(x-1>=0 && grid[x-1][y]=='1'&&!visit[x-1][y]){
                        que.push({x-1,y});
                       visit[x-1][y]=true;
                    }
                    if(y+1<m && grid[x][y+1]=='1'&&!visit[x][y+1]){
                        que.push({x,y+1});
                        visit[x][y+1]=true;
                    }
                    if(y-1>=0 && grid[x][y-1]=='1'&&!visit[x][y-1]){
                        que.push({x,y-1});
                        visit[x][y-1]=true;
                    }
                }//while
            }
        }
        return res;
    }

注意入队前就把周围的设置成以访问,会比出队后再设置标志节省很多时间

                  //周围四个入队,注意合法性检查
                 visit[x][y]=true;
                 if(x+1<n && grid[x+1][y]=='1'&&!visit[x+1][y]){
                     que.push({x+1,y});
                  }
                 if(x-1>=0 && grid[x-1][y]=='1'&&!visit[x-1][y]){
                     que.push({x-1,y});
                  }
                 if(y+1<m && grid[x][y+1]=='1'&&!visit[x][y+1]){
                     que.push({x,y+1});
                   }
                 if(y-1>=0 && grid[x][y-1]=='1'&&!visit[x][y-1]){
                     que.push({x,y-1});
                    }
             }//while

这样的话程序时间会越界,因为一次出队相当于访问了一个结点,很慢

当然广度优先搜索可以不借助访问数组,只要每次把岛屿的1设为0即可

相当于找到1后,前后左右符合要求的入队,入队前把岛屿设为0.下一次就不再访问到了

说白了bfs就是找到一个1就把前后左右消为0,然后下一次遍历

也可以bfs设置成函数

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        m = grid.size();
        if (m == 0)
            return 0;
        n = grid[0].size();
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (grid[i][j] == '1') {
                    grid[i][j] = '0';
                    res++;//更新结果
                    bfs(grid, i, j);
                }
            }
        }
        return res;
    }
private:
    int m, n;
    int res = 0;
    // grid必须可改变
    void bfs(vector<vector<char>>& grid, int i, int j) {
        queue<pair<int, int>> que;
        que.push({i,j});
        while (!que.empty()) {
            auto [x, y] = que.front();
            que.pop();
            // 把周围的1变成0
            if (x + 1 < m && grid[x + 1][y] == '1') {
                que.push({x + 1, y});
                grid[x + 1][y] = '0';
            }
            if (x - 1 >= 0 && grid[x - 1][y] == '1') {
                que.push({x - 1, y});
                grid[x - 1][y] = '0';
            }
            if (y + 1 < n && grid[x][y + 1] == '1') {
                que.push({x, y + 1});
                grid[x][y + 1] = '0';
            }
            if (y - 1 >= 0 && grid[x][y - 1] == '1') {
                que.push({x, y - 1});
                grid[x][y - 1] = '0';
            }
        }
    }
};

或者用4个方向

  // grid必须可改变
    int dx[4]={1,-1,0,0};
    int dy[4]={0,0,1,-1};
    void bfs(vector<vector<char>>& grid, int i, int j) {
        queue<pair<int, int>> que;
        que.push({i,j});
        while (!que.empty()) {
            auto [x, y] = que.front();
            que.pop();
            // 把周围的1变成0
            for(int i=0;i<4;i++){
                int nx=x+dx[i];
                int ny=y+dy[i];
                if(nx>=0 && ny>=0&& nx<m&& ny<n){
                    if(grid[nx][ny]=='1'){
                    que.push({nx,ny});
                    grid[nx][ny]='0';
                    }
                }
            }
        }
    }

被围绕区域

130. 被围绕的区域

思路一:利用dfs的visit标识

这里要从边界出发,从四周的O出发,把所有相连的O找到做好标记,这些O是不能被替换成X的。

这里不要回溯,dfs查找出所有值都是所求的

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        m = board.size();
        if (m == 0)
            return;
        n = board[0].size();
        this->board = board;
        this->visit = vector<vector<bool>>(m, vector<bool>(n, false));
        // 去边界dfs所有O
        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' && !visit[i][j]) {
                        dfs(i, j);
                    }
                }
            }
        }
        //内部没有visit标记的O标记成X
        for(int i=1;i<m-1;i++){
            for(int j=1;j<n-1;j++){
                if(board[i][j]=='O'&&!visit[i][j]){
                    board[i][j]='X';
                }
            }
        }
    }

private:
    int m, n;
    vector<vector<char>> board;
    vector<vector<bool>> visit;
    bool isInArea(int x, int y) { return x >= 0 && x < m && y >= 0 && y < n; }
    int d[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    void dfs(int x, int y) {
        visit[x][y] = true;
        for (int i = 0; i < 4; i++) {
            int nx = x + d[i][0];
            int ny = y + d[i][1];
            // 符合条件才dfs
            if (isInArea(nx, ny) && board[nx][ny] == 'O' && !visit[nx][ny]) {
                dfs(nx, ny);
            }
        }
    }
};

方法二:直接在棋盘上修改

把所有的边界O标记成A,然后只要是A就还原O,其他还原X

这里一定要棋盘作为一个引用参数

class Solution {
public:
    void solve(vector<vector<char>>& board) {
        m = board.size();
        if (m == 0)
            return;
        n = board[0].size();
        //去边界dfs所有O
        // 先找两横
        for (int j = 0; j < n; j++) {
            dfs(board,0, j);
            dfs(board,m - 1, j);
        }
        // 再找两竖
        for (int i = 1; i < m - 1; i++) {
            dfs(board,i, 0);
            dfs(board,i, n - 1);
        }
        // 还原,O还原X,A还原O
        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]=='A'){
                    board[i][j]='O';
                }
            }
        }
    }

private:
    int m, n;
    bool isInArea(int x, int y) { return x >= 0 && x < m && y >= 0 && y < n; }
    int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};
    void dfs(vector<vector<char>>& board,int x, int y) {
        // 不符合条件return
        if (!isInArea(x, y) || board[x][y] != 'O') {
            return;
        }
        //在board做文章
        board[x][y]='A';
        for (int i = 0; i < 4; i++) {
            int nx = x + dx[i];
            int ny = y + dy[i];
            dfs(board,nx, ny);
        }
    }
};

注意:要么遍历前判断,要么在dfs前有一条不符合题意的return

    // 先找两横
     for (int j = 0; j < n; j++) {
         if(board[0][j]=='O')
         dfs(board,0, j);
         if(board[m-1][j]=='O')
         dfs(board,m - 1, j);
     }
     // 再找两竖
     for (int i = 1; i < m - 1; i++) {
         if(board[i][0]=='O')
         dfs(board,i, 0);
         if(board[i][n-1]=='O')
         dfs(board,i, n - 1);
     }
void dfs(vector<vector<char>>& board,int x, int y) {
     // 不符合条件return,针对:for的两个dfs
     if (board[x][y] != 'O') {
         return;
     }
     board[x][y]='A';
     for (int i = 0; i < 4; i++) {
         int nx = x + dx[i];
         int ny = y + dy[i];
         if(isInArea(nx,ny)&&board[nx][ny]=='O')
         dfs(board,nx, ny);
     }
 }

因为只有borad[i][j]=='O’才dfs,不然不是的也会被标记成A

如果主程序for提前判断了,就不用dfs不符合return 那个判断了

单词搜索

79. 单词搜索

这里需要用到回溯,因为是找一条路径,参数需要用到index表示当前第i个字符

index的范围是从:0,…,word.size()-1

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        this->m = board.size();
        if (m == 0)
            return false;
        this->n = board[0].size();
        this->board = board;
        this->word = word;
        visit = vector<vector<bool>>(m, vector<bool>(n, false));
        // 遍历
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
               if (!visit[i][j]) {
                    dfs(i, j, 0);
                }
                if (hasword)
                    return true;
            }
        }
        return false;
    }

private:
    string word;
    int m, n;
    vector<vector<char>> board;
    vector<vector<bool>> visit;
    bool hasword = false;
    bool isInArea(int x, int y) { return x >= 0 && x < m && y >= 0 && y < n; }
    int d[4][2] = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
    void dfs(int x, int y, int index) {
        if (board[x][y] != word[index])
            return;
        if (index == word.size()-1) {
            hasword = true;
            return;
        }
       
        for (int i = 0; i < 4; i++) {
            visit[x][y] = true;
            int nx = x + d[i][0];
            int ny = y + d[i][1];
            if (isInArea(nx, ny) && !visit[nx][ny]) {
                dfs(nx,ny,index+1);
            }
            visit[x][y]=false;
        }
        
    }
};

visit也可以在for外面

        visit[x][y] = true;
        for (int i = 0; i < 4; i++) {
            int nx = x + d[i][0];
            int ny = y + d[i][1];
            if (isInArea(nx, ny) && !visit[nx][ny]) {
                dfs(nx,ny,index+1);
            }
        }
        //for的四个方向都不可行,还原该标志
         visit[x][y]=false;

两种标志图示:

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最后一个字符满足时候直接判断;

void dfs(int x, int y, int index) {
  if (index == word.size() - 1 && board[x][y] == word[index]) {
      hasword = true;
      return;
  }
   //等价于:if (board[x][y] != word[index]){return;}
  if (board[x][y] == word[index]) {
      visit[x][y] = true;
      for (int i = 0; i < 4; i++) {
          int nx = x + d[i][0];
          int ny = y + d[i][1];
          if (isInArea(nx, ny) && !visit[nx][ny]) {
              dfs(nx, ny, index + 1);
          }
      }
      // for的四个方向都不可行,还原该标志 
      visit[x][y] = false;
  }
}

也可以不定义visit,直接让访问的原棋盘数字变成"."

void dfs(int x, int y, int index) {
  if (board[x][y] != word[index] || board[x][y] == '.')
      return;
  // 保证了:board[x][y]==word[index]
  if (index == word.size() - 1) {
      hasword = true;
      return;
  }
  char ch = board[x][y];
  for (int i = 0; i < 4; i++) {
      board[x][y] = '.';
      int nx = x + d[i][0];
      int ny = y + d[i][1];
      if (isInArea(nx, ny)) {
          dfs(nx, ny, index + 1);
      }
      //board[x][y] = ch;
  }
  // 回溯
  board[x][y] = ch;
}

或者:只要不符合就return

void dfs(int x, int y, int index) {
  if (!isInArea(x, y) || board[x][y] != word[index] || board[x][y] == '.')
      return;
  // 满足条件返回结果
  if (index == word.size() - 1) {
      hasword = true;
      return;
  }
  char ch = board[x][y];
  board[x][y] = '.';
  for (int i = 0; i < 4; i++) {
      int nx = x + d[i][0];
      int ny = y + d[i][1];
      dfs(nx, ny, index + 1);
  }
  // 回溯
  board[x][y] = ch;
}

五:bfs求最短最长问题

回溯法BFS和分支限界法BFS

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

最小基因变化

433. 最小基因变化

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

主要题目中出现最短最长字眼的搜索问题,一般都是用bfs来解决

用map记录字符串,第一次出现的就是最小的,用map存储后,后续不同位置变化出的相同字符串的层数不会更新

用set存储基因库的字符串,便于查找。

 int minMutation(string startGene, string endGene, vector<string>& bank) {
        //map来记录当前变化的处在哪一层
        unordered_map<string,int> depth;
        //st来判断当前string是否在基因库内
        unordered_set<string> st;
        for(auto& val:bank) st.insert(val);
        //队列用来bfs
        queue<string> que;
        que.push(startGene);
        depth[startGene]=0;//startGene没有变化
        while(!que.empty()){
            string tmp=que.front();
            que.pop();
            //开始进行8个位置和每个位置3种字符的变化
            for(int i=0;i<8;i++){
                for(auto ch:{'A','C','G','T'}){//for(auto ch:"ACGT")
                    //和自己一样的字符不要
                    if(tmp[i]==ch) continue;
                    string next=tmp;
                    next[i]=ch;
                    //next不能重复,而且必须在bank内
                    if(depth.count(next)||!st.count(next)) continue;
                    //更新next的层数,当前根加一
                    depth[next]=depth[tmp]+1;
                    //满足条件返回结果
                    if(next==endGene) return depth[next];
                    //入队
                    que.push(next);
                }
            }
        }//while
        return -1;
    }

当然也可以是满足条件执行,而不是不满足条件continue,break或return

                  // next不能重复,而且必须在bank内
                    if (!depth.count(next) && st.count(next)) {
                        depth[next] = depth[tmp] + 1;
                        // 满足条件返回结果
                        if (next == endGene)
                            return depth[next];
                        // 入队
                        que.push(next);
                    }

单词接龙

127. 单词接龙

非常类似最小基因变化,只不过返回的是序列长度,所以mp[begin]=1

在变化上:有字符串长度的位置可选择,每个位置可有26个字母可选

遍历26个小写字母利用ascii码:char ch='a';ch<='z';ch++

  //注意:endword不在wordList必须return
        if(!st.count(endWord)) return 0;
class Solution {
public:
    int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
        //定义set记录wordList,快速查找
        unordered_set<string> st(wordList.begin(),wordList.end());
        //注意:endword不在wordList必须return
        if(!st.count(endWord)) return 0;
        //定义map记录次数
        unordered_map<string,int> mp;
        //返回的是长度,需要算上自己
        mp.insert({beginWord,1});//mp[beginWord]=1
        queue<string> que;
        que.push(beginWord);
        //队列操作
        while(!que.empty()){
            string tmp=que.front();
            que.pop();
            int sz=tmp.size();
            //sz个位置,每个位置25种变化
            for(int i=0;i<sz;i++){
                for(char ch='a';ch<='z';ch++){
                    if(tmp[i]==ch) continue;
                    string next=tmp;
                    next[i]=ch;
                    //next必须不在mp中而且在st中
                    //不符合提前return
                    if(mp.count(next) || !st.count(next)) continue;
                    mp[next]=mp[tmp]+1;
                    //找到返回结果
                    if(next==endWord) return mp[next];
                    que.push(next);
                }
            }
        }
        return 0;
    }
}

或者:在找到next就判断

//sz个位置,每个位置25种变化
            for(int i=0;i<sz;i++){
                for(char ch='a';ch<='z';ch++){
                    if(tmp[i]==ch) continue;
                    string next=tmp;
                    next[i]=ch;
                    //找到返回结果
                    if(next==endWord) return mp[tmp]+1;
                    //next必须不在mp中而且在st中
                    if(!mp.count(next) &&st.count(next)){
                    mp[next]=mp[tmp]+1;
                    que.push(next);
                    }
                }
            }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/501074.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Solana 2024 投资新风口:挖掘 DeFi、硬件开发与交易创新

将区块链的技术红利带给所有用户&#xff0c;Solana 自 2017 年诞生以来就致力于赋予开发者、消费者、投资人等各路人士的优越应用体验。在“以太坊杀手”林立的公链竞争阶段&#xff0c;Solana 凭借高性能公链的独特定位&#xff0c;朝着去中心化、安全性、低成本的目标不断精…

SpringBoot实现RabbitMQ的定向交换机(SpringAMQP 实现Direct定向交换机)

文章目录 Direct 交换机特点实战声明交换及其队列(以注解方式)发消息 应用 上一篇文章中的 Fanout 模式&#xff0c;一条消息&#xff0c;会被所有订阅其交换机的队列都消费。 但是&#xff0c;在某些场景下&#xff0c;我们希望不同的消息被不同的队列消费。这时就要用到 Dir…

蓝桥杯day14刷题日记

P8707 [蓝桥杯 2020 省 AB1] 走方格 思路&#xff1a;很典型的动态规划问题&#xff0c;对于偶数格特判&#xff0c;其他的正常遍历一遍&#xff0c;现在所处的格子的方案数等于左边的格子的方案数加上上面格子的方案数之和 #include <iostream> using namespace std; …

WPF 路由事件 数据驱动 、Window 事件驱动

消息层层传递&#xff0c;遇到安装有事件侦听器的对象&#xff0c;通过事件处理器响应事件&#xff0c;并决定事件是否继续传递&#xff1b; 后置代码中使用AddHandler方法设置事件监听器&#xff0c;该方法的 第一个参数是指定监听的路由事件类型对象&#xff0c; 第二个参数…

企业数据资产管理的战略价值与实施策略

一、引言 数据资产不仅记录了企业的历史运营情况&#xff0c;更能够揭示市场的未来趋势&#xff0c;为企业的决策提供有力支持。因此&#xff0c;如何有效地管理和利用数据资产&#xff0c;已经成为企业竞争力的重要体现。本文将探讨企业数据资产管理的战略价值与实施策略&…

新能源充电桩站场视频汇聚系统建设方案及技术特点分析

随着新能源汽车的普及&#xff0c;充电桩作为新能源汽车的基础设施&#xff0c;其安全性和可靠性越来越受到人们的关注。为了更好地保障充电桩的安全运行与站场管理&#xff0c;TSINGSEE青犀&触角云推出了一套新能源汽车充电桩视频汇聚管理与视频监控方案。 方案采用高清摄…

SMART PLC温度变化率计算功能块(算法框图+代码)

SMART PLC文章控制专用PID请参考下面文章链接: https://rxxw-control.blog.csdn.net/article/details/136702516https://rxxw-control.blog.csdn.net/article/details/136702516 1、监控下温度变化率 2、温度变化率计算功能块 3、计算周期到达

PCA+DBO+DBSCN聚类,蜣螂优化算法DBO优化DBSCN聚类,适合学习,也适合发paper!

PCADBODBSCN聚类&#xff0c;蜣螂优化算法DBO优化DBSCN聚类&#xff0c;适合学习&#xff0c;也适合发paper&#xff01; 一、蜣螂优化算法 摘要&#xff1a;受蜣螂滚球、跳舞、觅食、偷窃和繁殖等行为的启发&#xff0c;提出了一种新的基于种群的优化算法(Dung Beetle Optim…

BGP实训

BGP基础配置实训 实验拓扑 注&#xff1a;如无特别说明&#xff0c;描述中的 R1 或 SW1 对应拓扑中设备名称末尾数字为 1 的设备&#xff0c;R2 或 SW2 对应拓扑中设备名称末尾数字为2的设备&#xff0c;以此类推&#xff1b;另外&#xff0c;同一网段中&#xff0c;IP 地址的主…

Harbor部署

Harbor部署 下载和安装 github下载地址&#xff1a;https://github.com/goharbor/harbor/releases 解压和配置 # 解压tgz包 tar -zxvf harbor-offline-installer-v2.10.1.tgz # 进入目录后进行复制配置文件 cd harbor/ # 创建一个配置文件 cp harbor.yml.tmpl harbor.yml …

RabbitMQ基础笔记

视频链接&#xff1a;【黑马程序员RabbitMQ入门到实战教程】 文章目录 1.初识MQ1.1.同步调用1.2.异步调用1.3.技术选型 2.RabbitMQ2.1.安装2.1.1 Docker2.1.1 Linux2.1.1 Windows 2.2.收发消息2.2.1.交换机2.2.2.队列2.2.3.绑定关系2.2.4.发送消息 2.3.数据隔离2.3.1.用户管理2…

金三银四面试题(七):JVM常见面试题(1)

JVM会有许多零碎但是却很高频的基础考题。牢记这些&#xff0c;才能保证不在面试中落后于人。 说说对象分配规则 这也是之前面试腾讯时候被问到的问题&#xff1a;请介绍JVM如何分配对象&#xff1f; 对象优先分配在Eden 区&#xff0c;如果Eden 区没有足够的空间时&#xf…

nysm:一款针对红队审计的隐蔽型后渗透安全测试容器

关于nysm nysm是一款针对红队审计的隐蔽型后渗透安全测试容器&#xff0c;该工具主要针对的是eBPF&#xff0c;能够帮助广大红队研究人员在后渗透测试场景下保持eBPF的隐蔽性。 功能特性 随着基于eBPF的安全工具越来越受社区欢迎&#xff0c;nysm也应运而生。该工具能保持各种…

简单线程池的实现

线程池的代码可以写的很复杂&#xff0c;这里就稍微简单一些 首先来看一下线程池的原则&#xff0c;下面的大框是服务器&#xff0c;而在服务器中维护一个任务队列。 然后在server中预先创建一批线程&#xff0c;这批线程和任务队列合在一起只用向外界提供一个入队列的接口。 …

【php程序开发从入门到精通】——搭建PHP开发环境

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;开发者-曼亿点 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 曼亿点 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a…

搜索与图论——Floyd算法求最短路

floyd算法用来求多源汇最短路 用邻接矩阵来存所有的边 时间复杂度O(n^3) #include<iostream> #include<cstring> #include<algorithm>using namespace std;const int N 20010,INF 1e9;int n,m,k; int g[N][N];void floyd(){for(int k 1;k < n;k ){f…

计算机网络(第八版)-第1章课后习题参考答案

计算机网络(第八版)-第1章课后习题参考答案 本文是对自己之前文章的格式化&#xff1a;https://blog.csdn.net/qq_46396470/article/details/132788972?spm1001.2014.3001.5502 T1-01 计算机网络向用户可以提供哪些服务&#xff1f; 连通性和共享 &#xff0c;例如音频&…

docker环境配置过程中的常见问题

1、pull镜像问题 docker pull jenkins/jenkins:lts Using default tag: latest Trying to pull repository docker.io/library/centos ... Get https://registry-1.docker.io/v2/library/centos/manifests/latest: Get https://auth.docker.io/token?scoperepository%3Alibr…

基于Spring Boot 3 + Spring Security6 + JWT + Redis实现接口资源鉴权

紧接上一篇文章&#xff0c;基于Spring Boot 3 Spring Security6 JWT Redis实现接口资源鉴权 系列文章指路&#x1f449; 系列文章-基于SpringBoot3创建项目并配置常用的工具和一些常用的类 项目源码&#x1f449; /shijizhe/boot-test 文章目录 1. 修改 UserDetailsServic…

(学习日记)2024.03.25:UCOSIII第二十二节:系统启动流程详解

写在前面&#xff1a; 由于时间的不足与学习的碎片化&#xff0c;写博客变得有些奢侈。 但是对于记录学习&#xff08;忘了以后能快速复习&#xff09;的渴望一天天变得强烈。 既然如此 不如以天为单位&#xff0c;以时间为顺序&#xff0c;仅仅将博客当做一个知识学习的目录&a…