重谈DFS

相信对于DFS深度优先遍历算法,大家都是不陌生的,并且就算你没有通过DFS这种说法,你也应该知道递归算法。没错,DFS就是一种递归算法,只不过好的DFS都会采取各种各样的剪枝来使算法复杂度尽可能的降低,事实证明,有了剪枝的DFS递归算法可以说在思路和时间空间复杂度的博弈当中取得了令人较为满意的结果。首先,递归算法是出了名的思路简单,但复杂度很高的算法,因为采用递归时我们把大部分工作交给了电脑来完成,我一直认为,采用递归算法时,我们可以解决一些表面上看来及其复杂的问题,而并不需要对其实现细节过度关心,也正是因为这一点,递归算法可能会对初学者造成一定的困惑,不过不用担心,在之后的学习过程中,这些困惑会一一解开。
今天我们来分享一个关于DFS算法的题目,是剑指offer中的第12题,题目如下:矩阵中的路径
由于很久没有做过DFS类型的题目,所以当我一开始拿到这道题的时候有些无处下手,而看来解析后恍然大悟,原来是一个思路很简单的DFS题目,首先我觉得一方面是因为我有一段时间没有刷算法题导致敏感度下降思路混乱,另一方面原因是拿到题目后并没有很好地将模型抽象出来,而是一味地盯着题目本身不放而并未沉下心去思考。对此,我想说的是:对于一个陌生的题目,没有思路时,不要一遍遍地读题干,抽象能力是很重要的,先大概思考一下属于什么类型,然后将该题对应算法框架中的关键要素抽象出来。比如,这道题最基本的特征就是你必须遍历完所有路径才能判断出答案,所以我们可以往遍历方向的算法来思考,然后这道题有很多隐藏的约束条件(必须在数组中移动,每次只能移动一格),还有一些显性约束(不能走“回头路”)。此时再联想遍历算法最常用的DFS,其基本框架就是:从某一点出发,然后做出选择(在这里就是上下左右移动)>>往下一层继续遍历>>撤销选择(回溯);当满足条件时返回true(或将答案加入结果集)

在此,我们需要注意几点:

  • 判断满足条件的代码要放在剪枝代码之后,否则在可能会导致错误
  • 不要直接写return DFS(),这样会导致的结果就是找到一个出错的就直接返回而不会往其他方向再尝试,而要写成if(DFS())return true,这样一来,就只有在找到正确答案时才会return,就确保了每一个选择都会检测到。
  • 在下一次回溯完成后一定要记得撤销选择(把之前做的改变复原)

下面贴上此题的代码

class Solution {
public:

    bool exist(vector<vector<char>>& board, string word) {
    int rows=board.size();
    int cols=board[0].size();
        for(int i=0;i<rows;i++){
            for(int j=0;j<cols;j++){
                // 这里不能直接写return dfs();因为要求的是遍历完所有点都不能满足时才返回false,
                // 如果直接写return dfs(),则遇到不合适的点就会直接返回,不会再遍历其他的点
                if(dfs(board,word,i,j,0))return true;
            }
        }
        return false;
    }
    bool dfs(vector<vector<char>>&board,string word,int i,int j,int k){
        // 如果满足条件,就返回true
        int rows=board.size();
        int cols=board[0].size();
        if(i<0||i>=rows||j<0||j>=cols||word[k]!=board[i][j]||board[i][j]=='\0')return false;
        if(k==word.size()-1)return true;
        //将该点标记为'\0',表示已访问
        board[i][j]='\0';
        int dx[4]={-1,0,1,0};
        int dy[4]={0,1,0,-1};
        //做选择,往下一层遍历,方向为上、右、下、左
        for(int q=0;q<4;q++){
            int m=i+dx[q];
            int n=j+dy[q];
            // 这里不能直接写return dfs();因为要求的是遍历完所有点都不能满足时才返回false,
            // 如果直接写return dfs(),则遇到不合适的点就会直接返回,不会再遍历其他的点
            if(dfs(board,word,m,n,k+1))return true;
        }
        // 撤销选择
        board[i][j]=word[k];
        return false;
    }
};

再贴一题
在这里插入图片描述
代码:

class Solution {
public:
    
    int movingCount(int m, int n, int k) {
        vector<vector<int>>vis(m,vector<int>(n,0));
        return dfs(m,n,0,0,k,vis);
    }
    int dfs(int m,int n,int i,int j,int k,vector<vector<int>>&vis){
       int res=0;
        if(i<m&&j<n&&(i/10+i%10+j/10+j%10)<=k&&vis[i][j]==0){
            vis[i][j]=1;
            res=1+dfs(m,n,i+1,j,k,vis)+dfs(m,n,i,j+1,k,vis);
        }
        return res;
    }
};

此题需要注意一点的是,那个res一定不能使用全局变量,因为dfs代码块中有 res=1+dfs(m,n,i+1,j,k,vis)+dfs(m,n,i,j+1,k,vis); 如果res是全局变量,则每次都会多加一个1,而res写在dfs内部时,每次res都被初始化为0,只有当前结果满足条件时才会+1。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值