发布:2021年9月23日22:06:07
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
示例 1:
输入:m = 3, n = 7
输出:28
示例 2:
输入:m = 3, n = 2
输出:3
解释:
从左上角开始,总共有 3 条路径可以到达右下角。
- 向右 -> 向下 -> 向下
- 向下 -> 向下 -> 向右
- 向下 -> 向右 -> 向下
示例 3:
输入:m = 7, n = 3
输出:28
示例 4:
输入:m = 3, n = 3
输出:6
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
提示:
1 <= m, n <= 100
题目数据保证答案小于等于 2 * 109
有关动态规划的思路总结,之前写过一个相关的题解博客:
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客
里面也有我写的其他动态规划题解,一些通用的步骤都是一样的,也可以作为参考,进行对照思考。
而本题的思路和我之前做的一个动态规划题目非常像:
参考:【算法-LeetCode】64. 最小路径和(动态规划;二维数组;滚动数组)_赖念安的博客-CSDN博客
两道题目都有共同的特点:dp[i][j]
的值都是由其相邻的正上方元素(dp[i-1][j]
)和相邻的正左方元素(dp[i][j-1]
)决定的。所以两者的动态转移方程也有相似之处。
在本题中,dp[i][j]
代表由起点(也就是点 [0,0]
)到点 [i][j]
有几条可行的路径(当然,按照题目要求只能向下或想右走)。
具体的分析步骤就不赘述了,和上面那道题的基本一样,只要根据我之前总结的那几个步骤一点点分析即可。说一下本题的总体核心思路:我们要走到点 [i][j]
,只能先到达其正上方或者正左方,所以只要知道到达 [i-1][j]
和 [i][j-1]
各有几条符合条件的路径,再将两者相加即可得到到达 [i][j]
总共有几条路径。
经过上面的总体分析,动态转移方程也能很快就写出来:dp[i][j] = dp[i-1][j] + dp[i][j-1]
。
而由动态转移方程可知,我们必须先初始化 dp
数组的第一行和第一列。
详细解释请看下方注释:
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
// 创建一个 m × n 的二维dp数组,并给每个元素都事先填充为1
let dp = Array.from({length: m}).map(
() => Array.from({length: n}).fill(1)
);
// 因为上面创建dp数组时其实就已经顺便完成了对dp数组第一行和第一列的初始化操作
// 所以这里就不用再特意初始化dp数组了,直接对 m x n 的网格进行由前到后的遍历
for(let i = 1; i < m; i++) {
for(let j = 1; j < n; j++) {
// dp[i][j]的值由其相邻的上方和相邻的左方元素推导出来
dp[i][j] = dp[i-1][j] + dp[i][j-1];
}
}
// 最后返回dp数组右下角的元素即可
return dp[m-1][n-1];
};
提交记录
执行结果:通过
62 / 62 个通过测试用例
执行用时:76 ms, 在所有 JavaScript 提交中击败了47.14%的用户
内存消耗:37.8 MB, 在所有 JavaScript 提交中击败了69.82%的用户
时间:2021/09/23 22:12
其实这道题的动态转移方程比较好推断,关键是要想明白 dp[i][j]
的含义。
或者说,我们得先想明白怎样定义
dp[i][j]
,才能用上动态规划的思路,我觉得这才是所有动态规划类型的题目的关键所在。
当然,这道题用到了二维的 dp
数组,而其实这里也能利用滚动数组的操作来把二维数组降为一维数组,从而降低空间消耗。有关滚动数组的过程我也在相关的博客中有提到,只是这个概念比较难用语言描述,最好还是通过观察实际的二维表格到压缩到一维表格的过程来理解才有更好的效果。
参考:【算法-LeetCode】322. 零钱兑换(动态规划;博采众长的完全背包问题详解;二维数组;滚动数组)_赖念安的博客-CSDN博客
参考:【算法-LeetCode】64. 最小路径和(动态规划;二维数组;滚动数组)_赖念安的博客-CSDN博客
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let dp = Array.from({length: n}).fill(1);
for(let i = 1; i < m; i++) {
for(let j = 1; j < n; j++) {
dp[j] = dp[j] + dp[j-1];
}
}
return dp[n-1];
};
提交记录
执行结果:通过
62 / 62 个通过测试用例
执行用时:68 ms, 在所有 JavaScript 提交中击败了80.40%的用户
内存消耗:37.5 MB, 在所有 JavaScript 提交中击败了95.08%的用户
时间:2021/09/23 23:23
可以看到,使用一维 dp
数组的时间和空间性能都显著提升了。但是其核心的思路还是不变的,只不过是重复利用了某一个一维数组。而我们可以重复利用这个数组的最关键因素就是:除了需要初始化的那些 dp
元素,其余所有的 dp[i][j]
都是通过其“前面”的元素计算得到的,或者说新的 dp[i][j]
会覆盖前一轮计算得到的 dp[i][j]
,所以一维数组中的某一个位置能够被重复利用。
基本上所有的二维
dp
数组都可以通过上面的思路化为一维数组。所以我觉得在研究一维dp
数组(滚动数组)前,应该先对二维的实现原理有充分的理解。
更新:2021年7月29日18:43:21
因为我考虑到著作权归属问题,所以【官方题解】部分我不再粘贴具体的代码了,可到下方的链接中查看。
更新:2021年9月23日22:14:29
参考:不同路径 - 不同路径 - 力扣(LeetCode)
【更新结束】
更新:2021年9月22日14:08:16
参考:【算法-LeetCode】53. 最大子序和(动态规划初体验)_赖念安的博客-CSDN博客
更新:2021年9月23日22:15:04
参考:【算法-LeetCode】64. 最小路径和(动态规划;二维数组;滚动数组)_赖念安的博客-CSDN博客
参考:【算法-LeetCode】322. 零钱兑换(动态规划;博采众长的完全背包问题详解;二维数组;滚动数组)_赖念安的博客-CSDN博客