题目链接选自力扣 : 不同路径
题目不难读懂, 就是遵循一个规则, 机器人从起点开始, 只允许向右或者向下行走, 不允许返回而最终到达终点时一共有多少种不同的路径. 比如在一个 2 * 3 的矩阵里, 从做烧焦起点(0,0) 到达终点右下角(1,2) 一共有多少种方法. 通过题目要求, 发现一共有三种不同路径的走法.
这和我们前面几道动态规划不一样了. 它从一个一维的线性表变成了一个二维的. 但整体的思路还是不变的. 根据我们的题目要求, 定义 dp[i][j] 表示 从起始位置( 0, 0 ) 到终点 ( i , j ) 一共有多少种不同的路径方法数.
虽然现在的 dp 表变成了一个二维的 dp 表, 但是还是可以采取我们的最近一步划分问题.
何为最近一步呢 ? 看了前面的几篇文章相信你也和我一样不陌生了. 那这里我就直接划分了.
当这个机器人想到达 ( i, j) 这个位置时就必须先到 ( i-1, j ) 的位置向下一步或者先到 ( i, j-1 ) 的位置后向前一步到达( i, j ) 位置. 那么此时 dp[i][j] 就分为了两种情况.
必须经过 ( i-1, j ) 然后向下一步到达终点, 因此到达 ( i-1, j ) 有多少种方法, 那么最终经过 ( i-1, j ) 到达终点就有几种方法.
例如 : 到达 ( i-1, j ) 因为不能向上回退, 只有一条路. A-> B -> C. 最终通过 C 点到达终点, 只是在这个路径方法上多了一个点, 并非多了一条不同的路径. 即 A-> B -> C-> G. 因此最终到达 ( i-1, j ) 位置有多少种不同的路径方法到达终点就有几种.
此时正好对应我们的状态表示, 即 dp[i-1][j]
同样的, 必须先经过( i, j-1 ) 位置后向前一步到达终点. 因此到达 ( i, j-1 ) 这个点有多少种方法最终到达终点就有几种方法.
例如 : A-> B-> F 或者 A-> E-> F 一共条不同的路径. 最终到达终点时的路径只不过是多加了一个点并非多了一条新的路径. 最终为 A-> B-> F->G 以及 A-> E-> F-> G.
根据状态表示, 到达 ( i, j-1 ) 这个点有多少种路径则到达终点有多少种, 即 dp[i][j-1]
最终的dp[i][j] 为两种最近情况的总和. 即 dp[i][j] = dp[i][j-1] + dp[i-1][j]
对于初始化, 目的就是为了防止我们填写 dp 表数据时发生越界. 比如在填写这个绿色五角星点时,. 根据我们的状态转移方程可以知道, 需要知道这个点的上一个位置和后一个位置的值. 但是很明显此时上一个位置时没有的越界了. 因此我们需要对它进行初始化防止后续填写 dp 表发生越界.
对于这个题, 我们可以采取简单除暴的直接初始化的方法. 也就是把第一行和第一列都进行初始化了. 这样在使用到的时候就不会越界了. 那么这个值该初始化为多少好呢 ?
细想这个路线. 对于第一行而言. 这上面的每一个点从起始点过来都只有一条路可以走. 即向右.
对于第一列而言. 这上面的每一个点从起始点过来也只有一条路可以走. 即向下. 因此初始化这一行和这一列时, 这上面的每一个点都应为 1.
但是这种方法还是比较麻烦的, 虽然它很直观. 为了将它放在填表的时候进行初始化, 根据上次讲解 解码方法 这个题时, 讲了一种多开一个格子的初始化方法. 这次依然用这种方法. 什么意思呢 ? 也就是让这个二维的 dp 数组多开一行多开一列.
红色的格子就是我们多开的一行和一列. 那么现在我们该如何进行初始化呢 ? 根据动态转移方程不难想. 当我们想初始化起始点的时候, 只需要知道它的上一个点和前一个点的路径数即可对吧 ?
上面说了, 原本这个矩阵的第一行和第一列上的每一个点都要为 1 才能保证我们填表的正确性. 在结合我们的动态转移方程. 也就是只需要保证起始点的前一个位置为 1 或者上一个位置为 1. 此时填表的时候就可以直接将起始点进行初始化了.
总的来说就需要保证起始点位置为 1. 通过状态方程在填表时就可以将原本矩阵的第一行和第一列进行初始化了.
这是一个二维的 dp 表. 从状态转移方程来说, 需要知道当前位置的前一个位置和上一个位置的路径总数. 因此填表的顺序毫无疑问是 从上往下填写每一行, 而每一行又从左往右填写
此时我们的 dp 表多开了一行和一列 变成了 dp[m+1][n+1], dp 表中终点的位置存放的值对应从起点到终点的不同路径数. 对应到我们的 dp 表中则为 dp[m][n]. ( 注意下标关系 )
class Solution {
public int uniquePaths(int m, int n) {
// 1. 创建 dp 表
// 注意多开了一行一列.
int[][] dp = new int[m + 1][n + 1];
// 2. 初始化
// 确保起始点为 1. 保证起始点的上一个位置或者前一个位置为 1即可正确填写后续 dp 表
dp[0][1] = 1; // 我选择原本矩阵起始点的上一个点.
// dp[1][0] = 1; // 也可以选择原本矩阵起点的前一个点
// 3. 填写 dp 表
for(int i = 1; i <= m; i++) {
for(int j = 1; j <= n; j++) {
// 根据状态转移方程进行填表
dp[i][j] = dp[i][j-1] + dp[i-1][j];
}
}
// 4. 确认返回值
return dp[m][n];
}
}