Leetcode62不同路径:带备忘录的递归法和动态规划法


链接1
链接2
链接3
链接4
链接5

1.题目描述

在这里插入图片描述
在这里插入图片描述

2.带备忘录的递归法

  1. 本方法两个重点:递归和备忘录
  2. 递归(自顶向下):以斐波那契数列为例,要求f(n),则需要先知道f(n-1)和f(n-2),以此类推,最终需要f(1)和f(2)这两个base case。得到这两个base case之后我们再逐层返回结果,即可得到f(n).
  3. 备忘录(重叠子问题):如果将上述递归以树的形式画出来,可以看出很多的树枝是重复的,这就是重复子问题,其缺点是重复计算,浪费运行时间。解决方法是利用备忘录,将计算过的节点存储起来,当其他分支需要用到该节点的时候直接调用即可。
  4. 有的人把带备忘录的递归法看成是动态规划的一种,即自顶向下的动态规划
  5. 疑问:带备忘录的递归法跟回溯法有什么区别?
  6. 回溯法一般是没有重叠子问题,比如全排列,所以无法剪枝从而复杂度很高
  7. 个人认为回溯法更多的是跟“路径”有关

2.1 代码

class Solution {
    HashMap<String,Integer> hm=new HashMap<String,Integer>();//泛型必须是引用类型
    // int[][] memo=new int[m][n];  //为什么数组不能写在这里?因为m和n没确定
    public int uniquePaths(int m, int n) {
        // HashSet<String> hm=new HashSet<String>();//定义为成员/类变量就不用了传参
        return dpath(m,n,1,1);
    }
    public int dpath(int m,int n,int i,int j){
        if(i==m && j==n){
            return 1;
        }
        if(i>m || j>n){
            return 0;
        }
        String st=i+","+j; //为什么用逗号就可以,用空字符串就不行???,因为当用空格时,i=13,j=0和i=1,j=30会被看成一样的!!!
        // String st=""+i+","+j;为什么用逗号就可以,用空字符串就不行???
        // String st=i+""+j;
        if(hm.containsKey(st)){  
            return hm.get(st);
        }else{
            hm.put(st,dpath(m,n,i+1,j)+dpath(m,n,i,j+1));
        }
        // dpath(m,n,i,j)=dpath(m,n,i+1,j)+dpath(m,n,i,j+1);  //左值和右值问题导致的require variable,found value
        // return dpath(m,n,i+1,j)+dpath(m,n,i,j+1); //需要进行剪枝
        return hm.get(st);

        
    }
}

思路来源:
从finish入手,其左边的格子到finish的路径为1,其上面的格子到finish的路径为1,受此启发,
坐标(i,j)到终点finish的路径等于坐标(i+1,j)和(i,j+1)到finish的路径之和
状态转移式
dpath(m,n,i,j)=dpath(m,n,i+1,j)+dpath(m,n,i,j+1)
base case
当(i,j)正好为终点finish时,路径为1;
当 i 和 j 任意一个超过对应边界时,路径为0.
目标
dpath(m,n,1,1),即从左上角到finish的路径总数
考虑到路径会有重叠,所以需要利用备忘录
一般使用HashMap键值对,键为唯一身份确认,值为需要的值
代码注意事项:

  1. 将多个数字拼成字符串用逗号而非空字符串
  2. 成员变量或类变量就无需作为函数参数,局部变量就需要作为函数参数
  3. 左值和右值问题

3.动态规划法

上节中我们是从finish入手,对应的是自顶向下的动态规划法
如果我们从start入手,那对应的是自底向上的动态规划法
对于坐标( i, j ),start到该点的路径数等于start到(i-1,j)和到(i,j-1)的路径数之和
状态转移式:dp[i,j]=dp[i-1,j]+dp[i,j-1]
其中dp[i,j]表示start到坐标(i,j)的路径数
base case
dp[1,*] = dp[*,1]=1;
dp[0,*] = dp[*,0]=0.
目标:dp[m,n]

3.1 代码

class Solution {
    public int uniquePaths(int m, int n) {
        int[][] dp=new int[m+1][n+1];
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                if(i==1 || j==1){
                    dp[i][j]=1;
                }else if(i==0 || j==0){
                    dp[i][j]=0;
                }else{
                    dp[i][j]=dp[i-1][j]+dp[i][j-1];
                }

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

代码注意事项:

  1. DP table的大小设置为(m+1,n+1),因为不想从索引0开始,麻烦
  2. for循环中的赋值语句要用if else if else来包括,即任意情况只能允许一种赋值,否则就会出现覆盖的情况,逻辑错误

4.比较总结

  1. 带备忘录的递归法是自顶向下的动态规划法
  2. 带DP table的迭代法是自底向下的动态规划法
  3. 上述两点分别对应不同的思考起点,比如前者就从finish出发,后者就从start出发
  4. 实测后者表现更好
  5. 后续继续补充

在这里插入图片描述

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值