"斐波那契,动态规划开始的地方"
开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第11天,点击查看活动详情
62. 不同路径 题目描述:一个机器人位于一个 网格的左上角 (起始点在下图中标记为 “Start” )。机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。问总共有多少条不同的路径?
| 看图 | 示例 |
|---|---|
| 输入: 输出: |
中规中矩的动态规划
最优子结构? 假设我们知道走到 节点共有 种路径,那么从 节点走到 节点仅有一种走法(向下移动一步);假设我们也知道走到 节点共有 种路径,那么从 节点走到 节点仅有一种走法(向右移动一步)。所以走到 节点的不同路径数就等于走到 节点的不同路径数加上走到 节点的不同路径数,即 就是我们要找的最优子结构。
1、确定 dp 状态数组
定义 是走到 节点的不同路径数,其中 。
2、确定 dp 状态方程
与之前讨论的最优子结构即为 状态方程,
3、确定 dp 初始状态
当 时,对于任意 ,都只有一种路径(只能横向走),故 ;
当 时,对于任意 ,都只有一种路径 (只能纵向走),故 。
NOTE: 是一个特殊的点(起始点),在后期的计算中, 并不参与,所以无须纠结 或 的问题。为方便 数组初始化,统一按照 处理。
4、确定遍历顺序
-
第一层循环从 到 ;
-
第二层循环从 到 。
NOTE: 先遍历 后遍历 ,或者先遍历 后遍历 均可。
5、确定最终返回值
回归到状态定义中, 是走到 节点的不同路径数。
6、代码示例
/**
* 空间复杂度 O(m*n)
* 时间复杂度 O(m*n)
*/
function uniquePaths(m: number, n: number): number {
const dp = Array.from({ length: m }, () => new Array(n).fill(1));
for(let i = 1; i < m; i++) {
for(let j = 1; j < n; j++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
调整遍历顺序
/**
* 空间复杂度 O(m*n)
* 时间复杂度 O(m*n)
*/
function uniquePaths(m: number, n: number): number {
const dp = Array.from({ length: m }, () => new Array(n).fill(1));
for(let j = 1; j < n; j++) {
for(let i = 1; i < m; i++) {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
return dp[m - 1][n - 1];
}
思维提升-排列组合
我们发现在 的格子中,一定要向下走 步才能到达第 行,一定要右走 步才能到达第 列。所以这道题目就变成了一道高中的排列组合题目,从 里选择 共有多少种方法,即 或 。
代码示例
/**
* 空间复杂度 O(m*n)
* 时间复杂度 O(m*n)
*/
function uniquePaths(m: number, n: number): number {
return factorial(m + n - 2) / factorial(m - 1) / factorial(n - 1);
};
function factorial(n: number) {
if (n <= 1) {
return 1;
}
return n * factorial(n - 1);
}