62.不同路径
题目链接:62. 不同路径
文档讲解:代码随想录/不同路径
视频讲解:视频讲解-不同路径
状态:已完成(1遍)
解题过程
看到题目的第一想法
我的想法是如果只能往右和往下走,那么到达每一个位置的方法就是由这个位置的上边和左边的位置方法之和确定的。
用动态规划五部曲:
- 确定dp数组以及下标的含义:dp[ i ][ j ]的定义为:到达 ( i , j )位置的方法数量。
- 确定递推公式:dp[i][j] = dp[i - 1][j] + dp[i][j-1];
- dp数组如何初始化:dp[0][j] = 1 、dp[i][0] = 1;(最左边和最上边只有一种方法能到)
- 确定遍历顺序:从递归公式中可以看出,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) {
let dp = new Array(m).fill(0).map(()=>new Array(n).fill(0));
//定义dp数组dp[i][j]
for(let i =0;i<m;i++){
dp[i][0] = 1;
}
for(let j =0;j<n;j++){
dp[0][j] = 1;
}
//因为数组要从0开始,所以坐标的横纵都减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];
};
提交没有问题!
看完代码随想录之后的想法
大体思路基本一致,不过可以在定义数组的时候就将其初始化为1。
看了讲解手搓代码如下:
/**
* @param {number} m
* @param {number} n
* @return {number}
*/
var uniquePaths = function(m, n) {
let dp = new Array(m).fill(1).map(() => new Array(n).fill(1));
// dp[i][j] 表示到达(i,j) 点的路径数
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];
};
总结
这道题递推公式倒是好像,就是在初始化的时候需要额外考虑一下必须得初始化为1。
63. 不同路径 II
题目链接:63. 不同路径 II
文档讲解:代码随想录/不同路径 II
视频讲解:视频讲解-不同路径 II
状态:已完成(1遍)
解题过程
看到题目的第一想法
这道题和上一道题不一样的地方就在于有障碍物,那么障碍物上边和左边的所有坐标的dp数组是不受影响的,重点关注障碍物的右边位置和下边位置的坐标,到达这两个点的路径数量分别只有从上方到达和从左方到达,那么我试试将障碍物dp[x,y]设置为0试试看能不能行。
用动态规划五部曲:
- 确定dp数组以及下标的含义:dp[ i ][ j ]的定义为:到达 ( i , j )位置的方法数量。
- 确定递推公式:dp[i][j] = dp[i - 1][j] + dp[i][j-1];
- dp数组如何初始化:dp[0][j] = 1 、dp[i][0] = 1;(最左边和最上边只有一种方法能到)且障碍物位置dp[x][y] = 0;且边界如果出现障碍物dp[x][0],那么意味着dp[x+1][0]一直到dp[m-1][0]都得是0,因为边界出现障碍物,沿着边界往后走的路是被堵死的。
- 确定遍历顺序:从递归公式中可以看出,dp[i][j]是依赖 dp[i - 1][j] 和 dp[i][j-1],那么遍历的顺序一定是从前到后遍历的;
- 举例推导dp数组。
手搓代码如下:
/**
* @param {number[][]} obstacleGrid
* @return {number}
*/
var uniquePathsWithObstacles = function (obstacleGrid) {
if(obstacleGrid[0][0] == 1)return 0;
let m = obstacleGrid.length, n = obstacleGrid[0].length;
let dp = new Array(m).fill(1).map(() => new Array(n).fill(1));
let minSideX = [], minSideY = [];
for (let i = 0; i < m; i++) {
for (let j = 0; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
dp[i][j] = 0;
if (i == 0) {
minSideX.push(j);
//记录边界是否出现障碍物
} else if (j == 0) {
minSideY.push(i);
}
}
}
}
//边界出现障碍物意味着沿着边界之后的路都被堵死了
if (minSideY.length != 0) {
for (let i = minSideY[0]; i < m; i++) {
dp[i][0] = 0;
}
}
if (minSideX.length != 0) {
for (let j = minSideX[0]; j < n; j++) {
dp[0][j] = 0;
}
}
// dp[i][j] 表示到达(i,j) 点的路径数
for (let i = 1; i < m; i++) {
for (let j = 1; j < n; j++) {
if (obstacleGrid[i][j] == 1) {
//此处是障碍物坐标
continue;
} else {
dp[i][j] = dp[i - 1][j] + dp[i][j - 1];
}
}
}
return dp[m - 1][n - 1];
};
提交成功!
看完代码随想录之后的想法
前面一大串的初始化其实在for循环里就可以完成。。。给for循环的条件加一个限定就可以了。
而且给障碍物坐标设置0也可以在双层for循环中进行。。确实精简太多了。
讲解代码如下:
/**
* @param {number[][]} obstacleGrid
* @return {number}
*/
var uniquePathsWithObstacles = function(obstacleGrid) {
const m = obstacleGrid.length
const n = obstacleGrid[0].length
const dp = Array(m).fill().map(item => Array(n).fill(0))
for (let i = 0; i < m && obstacleGrid[i][0] === 0; ++i) {
dp[i][0] = 1
}
for (let i = 0; i < n && obstacleGrid[0][i] === 0; ++i) {
dp[0][i] = 1
}
for (let i = 1; i < m; ++i) {
for (let j = 1; j < n; ++j) {
dp[i][j] = obstacleGrid[i][j] === 1 ? 0 : dp[i - 1][j] + dp[i][j - 1]
}
}
return dp[m - 1][n - 1]
};
总结
这道题和没障碍物的题主要区别和注意点就在于一个是障碍物坐标的dp要设置为0;一个是障碍物如果出现在左边界和上边界的时候,沿着边界之后的坐标的dp要初始化为0。