lc62.不同路径/ lc63.不同路径Ⅱ(记忆化DFS、二维动态规划)

今天leetcode日常打卡,题目是leetcode63.不同路径Ⅱ。考虑到这道题是62.不同路径 的进阶版,就先把62做了,再做的63。特此记录做这两道题的过程和一些自己的做题总结。

62.不同路径

其实,第一眼看到这个想到的就是DFS,因为这道题每一步(机器人移动)都需要做出选择,每做出一次选择有一个分支。如果做出选择A,得到结果不是我想要的,那么我就返回到做出选择A之前的那个时间点。做出另一个选择B,看是否能得到我想要的结果。如同走迷宫一样,能走通,就继续走,走不通就要原路返回,再走另外一条路。

class Solution {
private:
    int count=0;
public:
    int uniquePaths(int m, int n) {//m是列,n是行
        helper(m,n,0,0);
        return count;
    }

    void helper(int m,int n,int cx,int cy){
        //如果做出的选择能最终走到终点,那么证明这个选择是对的,计数+1,同时回到做出选择的那个点
        //以便做出另一个选择,判断另一个选择是不是符合要求
        if((cx==m-1)&&(cy==n-1)){
            count++;
            return;//回到做出选择的那个点
        }
            
        if(cx>m-1) return;//如果越界了,意味着这条路明显走不通,立即返回
                          //如同走迷宫时,碰到了死胡同,那肯定得原路返回,走另外一条路
        if(cy>n-1) return;

        helper(m,n,cx+1,cy);//向下移动
        helper(m,n,cx,cy+1);//向右移动

    }
};

跑了几个简单的例子,都过了,以为没问题。结果已提交,直接超时。再参考了评论区讲解,发现了超时是因为做了很多已经做过的工作。为了更好的理解,超时的原因,特意画了下面的图方便理解(其实题解里有人做了解释,但是树状的图加上单纯的数字有点不够直观)。

下面是加了备忘录的DFS

class Solution {
public:
    int uniquePaths(int m, int n) {//m是列,n是行
        
        vector<vector<long long>> mem(m,vector<long long>(n,-1));

        return helper(m,n,0,0,mem);;
    }

    int helper(int m,int n,int cx,int cy,vector<vector<long long>>& mem){
        if((cx==m-1)&&(cy==n-1)){
            return 1;
        }

        if(cx>m-1) return 0;
        if(cy>n-1) return 0;
        
        //发现当前位置被打了标记,就不需要走这条路,直接返回之前记录的结果
        if(mem[cx][cy]!=-1){
            return mem[cx][cy];
        }

        long long total=0;//用long long,是因为m、n太大时,导致结果超出了int范围
        total+=helper(m,n,cx+1,cy,mem);//向下移动
        total+=helper(m,n,cx,cy+1,mem);//向右移动
        
        //打上标记,方便后面做参考
        mem[cx][cy]=total;
        return total;
    }
};

这道题还可以采用动态规划的解法

动态规划的本质就是填表格,尤其是对于这种二维动态规划,根据填表格能够更加清楚地退出状态转移

对于当前位置(i,j)来说,机器人可能是从上边过来的,也可能是左边过来的。

根据问题,定义状态dp[i][j],表示到达位置(i,j)的路径数

那么状态转移方程可以很容易得到,dp[ i ][ j ]=dp[ i-1 ][ j ]+dp[ i ][ j-1 ]

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(n,vector<int>(m,0));

        //初始化第一行
        //对于第一行来说,每一个位置只能从左边移动过来,只有一条路
        for(int i=0;i<m;i++){
            dp[0][i]=1;
        }

        //初始化第一列
        //对于第一列来说,每一个位置只能从上边移动过来,只有一条路
        for(int i=0;i<n;i++){
            dp[i][0]=1;
        }

        for(int i=1;i<n;i++){
            for(int j=1;j<m;j++){
                dp[i][j]=dp[i][j-1]+dp[i-1][j];
            }
        }

        return dp[n-1][m-1];
    }
};

63.不同路径Ⅱ

这道题多了一个限制条件,就是障碍物是否存在。根据62的题解,同样可以用记忆化的DFS动态规划求解

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {     
        int rows=obstacleGrid.size();
        if(rows==0) return 0;
        int cols=obstacleGrid[0].size();
        if(obstacleGrid[0][0]==1) return 0;

        vector<vector<int>> mem(rows,vector<int>(cols,-1));
        
        return helper(obstacleGrid,rows,cols,0,0,mem);;
    }

    int helper(vector<vector<int>>& obstacleGrid,int rows,int cols,int row,int col,vector<vector<int>>& mem){
        //在到达目的地的基础上,目的地必须不存在障碍物
        if((row==rows-1)&&(col==cols-1)&&(obstacleGrid[row][col]==0)){
            return 1;
        }
        
        //在越界的基础上,如果存在障碍物代表这条路不同
        if(row>=rows||col>=cols||(obstacleGrid[row][col]==1)) return 0;
        
        //调用之前记录的结果
        if(mem[row][col]!=-1){
            return mem[row][col];
        } 

        int total=0;
        total+=helper(obstacleGrid,rows,cols,row+1,col,mem);
        total+=helper(obstacleGrid,rows,cols,row,col+1,mem);

        mem[row][col]=total;
        return total;
    }
};

动态规划的解法主要注意初始化状态转移

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int rows=obstacleGrid.size();
        if(rows<1) return 0;
        int cols=obstacleGrid[0].size();
        if(obstacleGrid[0][0]==1) return 0;//路口就是障碍物,直接返回0(这个是真的坑)
        
        //全部初始化为1,即默认对于每个位置而言,都至少有一条路径可以到达
        vector<vector<int>> dp(rows,vector<int>(cols,1));

        //第一列
        for(int i=1;i<rows;i++){
            //当前位置有障碍物,则不可能有路径可以抵达当前位置
            //或者之前的状态为0,意味着前一个位置无法抵达,那么当前位置肯定也无法抵达
            if((obstacleGrid[i][0]==1)||(dp[i-1][0]==0)) dp[i][0]=0;
        }

        //第一行
        for(int i=1;i<cols;i++){
            if((obstacleGrid[0][i]==1)||(dp[0][i-1]==0)) dp[0][i]=0;
        }
        
        for(int i=1;i<rows;i++){
            for(int j=1;j<cols;j++){
                //如果当前位置本身就存在障碍物,那么无论如何都办法到达当前位置
                if(obstacleGrid[i][j]==1) dp[i][j]=0;
                //如果当前位置没有障碍物,那么状态取决于上边和左边
                else dp[i][j]=dp[i-1][j]+dp[i][j-1];
            }
        }

        return dp[rows-1][cols-1];
        
    }
};

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值