力扣 62.不同路径 从DFS到动态规划

直接上题:

原题链接:    力扣 62.不同路径

题意是有一个机器人,和一个 m x n 的网络,机器人每次只能 往下走 或者 往右走 一步

问从 (1,1)位置开始走到(m,n)位置有多少种不同的路径。

直接看题解:

memset(f, 0, sizeof f);
f[m][n] = 1;

for (int i = m; i >= 1; i--)
    for (int j = n; j >= 1; j--)
        f[i][j] += f[i + 1][j] + f[i][j + 1];

cout << f[1][1];

相信很多没学过的小白都看不懂,那今天我们就从递归开始,看看这段代码是怎么来的。

 

DFS:

        首先,如果说我要用递归写这道题,那递归的定义是什么?

       ① 我们先看看有什么可变参数,机器人会走,那他当前所在位置肯定是会变的,那我们需要一个(i,j)表示他当前在哪,这样,我们递归的定义就可以表示为:从(i,j)位置走到(m,n)的不同路径数

        ②再问,机器人可以怎么走?题目里说了,机器人可以往下走往右走。

        ③走到什么时候停?走到(m,n)的时候停

        那我们就可以写出这样的代码:

//返回在(i,j)位置走到(m,n)位置的不同路径方案总数
int dfs(int i, int j)
{
    //如果走到(m,n)了,返回 1,表示这是一种方案
    if (i == m && j == n)
        return 1;
    
    //如果走出边界了,返回0
    if (i > m || j > n)
        return 0;
    
    //机器人可以往下走,也可以往右走
    int p1 = dfs(i + 1, j);  //往下走,j 不变,i + 1 
    int p2 = dfs(i, j + 1);  //往右走,i 不变,j + 1 
    
    //返回往下走和往右走的路径数加起来
    return p1 + p2;
}

我们可以检验一下写的对不对:

输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右
3. 向下 -> 向右 -> 向下

89d998a72f2947cc85f0eff617ccf7a2.png

 

有人问?为什么(i == m && j == m) 的时候返回 1。我们可以看看递归的图:

b024e8559b5546cebee1ff983ea50ef8.png

 

这就是递归的原理。但是我们又发现,这样做所耗的时间非常大,为什么?我们可以观察到图中(2,2)来到了两次,这是递归,每次来到都会重新计算,在上图中的 m,n 还是小的,要是我 m,n 很大呢?那会有多少的重复计算?因此,我们就诞生了记忆化搜索。

 

记忆化搜索:

        所谓的记忆化搜索,其实就是让递归只算一次,第一次计算后我们就储存他的值,之后再次来到我们就可以直接用储存的值,而不是重新计算。

        应该怎么储存他的值呢?我们看到递归有两个参数,那我们也应该要一个二维数组来存储他的值。上代码:

//这个二维数组用来存储值
int f[110][110];  //这里演示代码就只开 110 x 110 的空间示例一下就行了
memset(f, -1, sizeof f);  //将 f[][] 所有值设为 -1,用来判断有没有计算过

int dfs(int i, int j)
{
    //  如果 f[i][j] 不是 -1,说明之前算过,我们直接返回表里的值
    if (f[i][j] != -1)
        return f[i][j];

    if (i == m && j == n)
        return f[i][j] = 1;  // return 之前先存值
    
    if (i > m || j > n)
        return f[i][j] = 0;  //存值
    
    int p1 = dfs(i + 1, j);  //往下走
    int p2 = dfs(i, j + 1);  //往右走 
    
    f[i][j] = p1 + p2;  //在 return 之前,先将计算好的值存起来
    return f[i][j];
}

这样记忆化搜索的代码就写好了,我们可以发现,他返回的都是 f[i][j] ,所以这个代码其实就是在填 f[i][j] 这张表。

 

动态规划:

让我们先看看这张表:

                以 m = 3,n = 2 为例(表中没有填的值都初始化成了 0)

行号

列号

0123
0    
1 31 
2 21 
3 11 
4    

根据之前记忆化搜索的定义,当 i == m && j == n 时,f[i][j] = 1,也就是这张表中的第 3 行,第 2 列的值为 1。又根据之前的代码,dfs(i, j) 返回的值是 dfs(i + 1, j) + dfs(i, j + 1),实际上就是(i,j)位置上的值是由 (i + 1,j)位置 和 (i,j + 1)位置的值推出来的,我们返回的是 f[i][j],用的都是表的值,所以我们可以得出

f[i][j] = f[i + 1][j] + f[i][j + 1];

按照这个规律,我们就可以填出上面那张表。

试着这样想,我是不是把 f[m][n] 设置成 1,紧接着一步步把表里的值推出来,最后输出 f[1][1] 就行了?首先观察上面式子:

        f[i][j] = f[i + 1][j] + f[i][j + 1]

f[i][j] 是依赖于 f[i + 1][j] 与 f[i][j + 1] 的,所以我们写代码应该 由下往上由右往左 遍历,先将 f[i + 1][j] 与 f[i][j + 1] 填好,再填 f[i][j] ,我们就可以得出以下代码:

    f[i][j] 就表示从 (i,j)位置走到(m,n)位置的不同路径数

    f[i][j] = f[I + 1][j] + f[i][j + 1] 就是状态转移方程

//初始化
memset(f, 0, sizeof f);
f[m][n] = 1;

for (int i = m; i >= 1; i--)  //由下往上
    for (int j = n; j >= 1; j--)  //由右往左
        f[i][j] += f[i + 1][j] + f[i][j + 1];

//输出 f[1][1]
cout << f[1][1];

这样我们就把代码从 DFS 改成 记忆化搜索 再改成 动态规划 啦。

试试水:

49f5f97aaede47d89f938ca62eda1dbe.png

这样子我们就把这道题     力扣 62.不同路径 写完了。

        第一次写博客不太会,有欠缺大家可以在评论区说出来,谢谢啦

 

 

 

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值