持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第14天,点击查看活动详情.
62. 不同路径
题目描述
一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 “Start” )。
机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为 “Finish” )。
问总共有多少条不同的路径?
解题思路
思路一: 排列组合
从左上角到右下角的过程中,我们需要移动 m+n−2 次,其中有 m−1 次向下移动,n−1 次向右移动。因此路径的总数,因此,从m+n-2次步数中选出m-1次向下的步数,或者n-1次向右的步数便是我们要的结果,因此不同的路径数就等于 或者是,两者相等。
因此,我们根据公式直接求解即可:
有了这个公式后,实现代码如下:
由于我们交换行列的值并不会对答案产生影响,因此我们总可以通过交换 m 和 n 使得 m ≤ n,这样空间复杂度降低至 O(min(m,n))
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let numDown = m - 1, numRight = n - 1;
if(numRight == 0 || numDown == 0){
return 1;
}
let numSum = numRight + numDown,
min = Math.min(numDown, numRight);
let numerator = 1 , denominator = 1;
for (let i = 0; i < min; i++) {
numerator *= (numSum - i);
denominator *= (min - i);
}
return Math.floor(numerator / denominator);
};
- 空间复杂度: O(min(m,n))
- 空间复杂度:O(1)
思路二: 动态规划
dp[i][j]表示从位置(i,j)到终点的路径个数
-
我们从终点开始,已知终点到终点只有一条路径,因此 初始值:
dp[m-1][n-1]=1 -
然后考虑终点正上方: 终点的正上方想要到达终点,只能选择向下走,因此路径个数仍然为正下方的路径个数
dp[i][n-1] = dp[i+1][n-1] -
考虑终点正左方: 同理,终点的正左方想要到达终点,只能选择向右走,因此路径个数仍然为正右方的路径个数
dp[m-1][i] = dp[m-1][i+1] -
对于其他位置,当前路径数量等于 右方位置路径数 + 下方位置路径数 之和,即
dp[i][j] = dp[i+1][j]+ dp[i][j+1];
实现代码如下:
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
if(m == 1 || n == 1){
return 1;
}
const dp = Array(m).fill(0).map(() => Array(n).fill(0));
dp[m-1][n-1] = 1;
for (let i = n - 2; i >= 0; i--) {
dp[m - 1][i] = 1;
}
for (let i = m - 2; i >= 0; i--) {
dp[i][n - 1] = 1;
}
for (let j = n -2; j >= 0; j--) {
for (let i = m-2; i >= 0; i--) {
// 当前路径数量等于 右边+下边的数量之和
dp[i][j] = dp[i+1][j]+ dp[i][j+1];
}
}
return dp[0][0];
};
-
时间复杂度:O(mn)
-
空间复杂度:O(mn)
优化一
由于dp[i][j] = dp[i+1][j]+ dp[i][j+1],因此只需要保留当前行与下一行的数据 (在动态方程中,即pre[j] = dp[i + 1][j])
优化代码如下:
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let prev = Array(n).fill(1), curr = Array(n).fill(1);
for (let i = m - 2; i >= 0;i--){
for (let j = n - 2; j >= 0; j--){
curr[j] = curr[j + 1] + prev[j];
}
prev = [...curr];
}
return prev[0];
};
优化后空间复杂度:O(2n);
优化二
只使用一个数组,cur[j] = cur[j + 1] + cur[j] 未赋值之前右边的cur[j] 始终表示当前行第i行的下一行第j列的值,赋值之后左边的cur[j]表示当前行第i行第j列的值,cur[j + 1] 表示当前行第i行第j + 1列的值(cur[j + 1] 在计算cur[j]之前就已经计算了,所以表示的是当前行而不是下一行
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let curr = Array(n).fill(1);
for (let i = m - 2; i >= 0;i--){
for (let j = n - 2; j >= 0; j--){
curr[j] += curr[j + 1];
}
}
return curr[0];
};
优化后空间复杂度 O(n)