LeetCode不同路径
Posted 有顶天の甲子园
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode不同路径相关的知识,希望对你有一定的参考价值。
如图,m × n 的网格的左上角作为起点,每次只能向右或向下移动一格,最终要到达右下角。求有多少条可能的路径。
m,n 最大取 100。
我的想法是递归,分分钟实现
1 int uniquePaths(int m, int n) { 2 if (m == 1 || n == 1) return 1; 3 return uniquePaths(m - 1, n) + uniquePaths(m, n - 1); 4 }
然而数字稍微取大(m = 19,n = 13)就 Time Limit Exceeded 了。
于是这么想,以上图 3 × 7 的网格为例,可以向下移动 2 格,向右移动 6 格,一共需要移动 8 格。那么只需要在 8 格中任意挑选 2 格作为向下移动,另外 6 格都向右即可。每种不同的选择表示了一条不同的路径。
那么可以用排列组合公式。m 和 n 表示向右或向下移动的次数,第二个等号是为了减少计算次数,因为 m!、n!、(m+n)! 阶乘计算时重复计算了一些数字。
1 int uniquePaths(int m, int n) { 2 m--; n--; 3 int small = min(m, n), big = max(m, n); 4 return fac(big + 1, big + small) / fac(1, small); 5 } 6 /* 由于m、n能取到100,使用int计算连乘会溢出 */ 7 long long fac(int start, int end) { 8 long long result = 1; 9 for (int i = start; i <= end; i++) 10 result *= i; 11 return result; 12 }
同时注意到公式还可以继续化简,这样就只需要用一个循环语句。
但这种算法用到了 n 次除法,得注意将计算结果存储在 double 型变量中,因为 int 型的 (m + i) / i 会得出一个整数结果而导致计算过程中数据错误。
如果定义 int result = 1,第 6 行为 result *= (big + i) / i,计算会出错。例如 m = 4,n = 4 时,会输出 16,而正确答案是 20。
即使将 result 改为 double 型,第 6 行改为 result *= (double)(big + i) / i,也会出错。例如 m = 10,n = 10 时,会输出 48619,而正确答案是 48620。
1 int uniquePaths(int m, int n) { 2 m--; n--; 3 int small = min(m, n), big = max(m, n); 4 double result = 1.0; 5 for (int i = 1; i <= small; i++) { 6 result = result * (big + i) / i; 7 } 8 return (int)result; 9 }
因此输入输出均为 int 型的计算中,若计算过程用到多次很可能无法除尽的除法时,得非常小心。
方法二:
动态规划
这是一个基本的动态规划问题。
由于只能向右或向下移动,那么到达一个格子的时候只有可能是两种情况:
- 从上边一格向下移动到这一格;
- 从左边一格向右移动到这一格。
假设移动到 (i, j) 这一格的不同路径数为 P[i][j],显然,P[i][j] = P[i - 1][j] + P[i][j - 1]。边界条件是最左边一列(无法从更左边移动过来)和最上边一行(无法从更上边移动过来),但显然对于所有 i,j 有 P[0][j] = 1,P[i][0] = 1。
1 int uniquePaths(int m, int n) { 2 vector<vector<int>> path(m, vector<int> (n, 1)); 3 for (int i = 1; i < m; i++) 4 for (int j = 1; j < n; j++) 5 path[i][j] = path[i - 1][j] + path[i][j - 1]; 6 return path[m - 1][n - 1]; 7 }
这种算法的时间复杂度是 O(m * n),空间复杂度也是 O(m * n),效率较低。
注意到每次更新 P[i][j] 的值,只需要用到 P[i - 1][j](同一列)和 P[i][j - 1](左边一列),因此只需要维护两列元素而不需要维护整个 m × n 矩阵。
1 int uniquePaths(int m, int n) { 2 if (m > n) return uniquePaths(n, m); // 这种方法比比较 m、n 大小并交换或者取较大、较小值更高明! 3 vector<int> left(n, 1), right(n, 1); 4 for (int i = 1; i < m; i++) { 5 for (int j = 1; j < n; j++) 6 right[j] = left[j] + right[j - 1]; 7 swap(left, right); 8 } 9 return left[n - 1]; 10 }
这种算法的空间复杂度优化到 O(min(m, n))。又发现,对两列执行完循环后移动到下面两列时,左边一列 left 就是上一轮 right 交换过来的,因此只需要维护一列即可。
1 int uniquePaths(int m, int n) { 2 if (m > n) return uniquePaths(n, m); 3 vector<int> path(n, 1); 4 for (int i = 1; i < m; i++) { 5 for (int j = 1; j < n; j++) 6 path[j] += path[j - 1]; 7 } 8 return path[n - 1]; 9 }
以上是关于LeetCode不同路径的主要内容,如果未能解决你的问题,请参考以下文章