直接上题:
原题链接: 力扣 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. 向下 -> 向右 -> 向下
有人问?为什么(i == m && j == m) 的时候返回 1。我们可以看看递归的图:
这就是递归的原理。但是我们又发现,这样做所耗的时间非常大,为什么?我们可以观察到图中(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)
行号 列号 | 0 | 1 | 2 | 3 |
0 | ||||
1 | 3 | 1 | ||
2 | 2 | 1 | ||
3 | 1 | 1 | ||
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 改成 记忆化搜索 再改成 动态规划 啦。
试试水:
这样子我们就把这道题 力扣 62.不同路径 写完了。
第一次写博客不太会,有欠缺大家可以在评论区说出来,谢谢啦