如图,m × n 的网格的左上角作为起点,每次只能向右或向下移动一格,最终要到达右下角。求有多少条可能的路径。

m,n 最大取 100。

我的想法是递归,分分钟实现

 int uniquePaths(int m, int n) {
if (m == || n == ) return ;
return uniquePaths(m - , n) + uniquePaths(m, n - );
}

然而数字稍微取大(m = 19,n = 13)就 Time Limit Exceeded 了。

于是这么想,以上图 3 × 7 的网格为例,可以向下移动 2 格,向右移动 6 格,一共需要移动 8 格。那么只需要在 8 格中任意挑选 2 格作为向下移动,另外 6 格都向右即可。每种不同的选择表示了一条不同的路径。

那么可以用排列组合公式。m 和 n 表示向右或向下移动的次数,第二个等号是为了减少计算次数,因为 m!、n!、(m+n)! 阶乘计算时重复计算了一些数字。

 int uniquePaths(int m, int n) {
m--; n--;
int small = min(m, n), big = max(m, n);
return fac(big + , big + small) / fac(, small);
}
/* 由于m、n能取到100,使用int计算连乘会溢出 */
long long fac(int start, int end) {
long long result = ;
for (int i = start; i <= end; i++)
result *= i;
return result;
}

同时注意到公式还可以继续化简,这样就只需要用一个循环语句。

但这种算法用到了 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。

 int uniquePaths(int m, int n) {
m--; n--;
int small = min(m, n), big = max(m, n);
double result = 1.0;
for (int i = ; i <= small; i++) {
result = result * (big + i) / i;
}
return (int)result;
}

因此输入输出均为 int 型的计算中,若计算过程用到多次很可能无法除尽的除法时,得非常小心。

方法二:

动态规划

这是一个基本的动态规划问题。

由于只能向右或向下移动,那么到达一个格子的时候只有可能是两种情况:

  1. 从上边一格向下移动到这一格;
  2. 从左边一格向右移动到这一格。

假设移动到 (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。

 int uniquePaths(int m, int n) {
vector<vector<int>> path(m, vector<int> (n, ));
for (int i = ; i < m; i++)
for (int j = ; j < n; j++)
path[i][j] = path[i - ][j] + path[i][j - ];
return path[m - ][n - ];
}

这种算法的时间复杂度是 O(m * n),空间复杂度也是 O(m * n),效率较低。

注意到每次更新 P[i][j] 的值,只需要用到 P[i - 1][j](同一列)和 P[i][j - 1](左边一列),因此只需要维护两列元素而不需要维护整个 m × n 矩阵。

 int uniquePaths(int m, int n) {
if (m > n) return uniquePaths(n, m); // 这种方法比比较 m、n 大小并交换或者取较大、较小值更高明!
vector<int> left(n, ), right(n, );
for (int i = ; i < m; i++) {
for (int j = ; j < n; j++)
right[j] = left[j] + right[j - ];
swap(left, right);
}
return left[n - ];
}

这种算法的空间复杂度优化到 O(min(m, n))。又发现,对两列执行完循环后移动到下面两列时,左边一列 left 就是上一轮 right 交换过来的,因此只需要维护一列即可。

 int uniquePaths(int m, int n) {
if (m > n) return uniquePaths(n, m);
vector<int> path(n, );
for (int i = ; i < m; i++) {
for (int j = ; j < n; j++)
path[j] += path[j - ];
}
return path[n - ];
}

【LeetCode】不同路径的更多相关文章

  1. LeetCode:路径总和II【113】

    LeetCode:路径总和II[113] 题目描述 给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径. 说明: 叶子节点是指没有子节点的节点. 示例:给定如下二叉树, ...

  2. LeetCode:路径总和【112】

    LeetCode:路径总和[112] 题目描述 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例 ...

  3. Leetcode 不同路径系列

    Leetcode不同路径系列题解笔记 62. 不同路径 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为 "Start" ). 机器人每次只能向下或者向右移动一 ...

  4. leetcode不同路径

    62. 一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” ). 机器人每次只能向下或者向右移动一步.机器人试图达到网格的右下角(在下图中标记为“Finish”). 问 ...

  5. LeetCode 112. 路径总和(Path Sum) 10

    112. 路径总和 112. Path Sum 题目描述 给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节 ...

  6. LeetCode(2)---路径总和

      给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标和 sum = ...

  7. LeetCode 112.路径总和(C++)

    给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标和 sum = 22 ...

  8. LeetCode 中级 - 路径总和2(113)

    给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径. 说明: 叶子节点是指没有子节点的节点. 示例:给定如下二叉树,以及目标和 sum = 22, 5 / \ 4 8 ...

  9. LeetCode 简单 - 路径总和(112)

    给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和. 说明: 叶子节点是指没有子节点的节点. 示例: 给定如下二叉树,以及目标和 sum = 22 ...

  10. leetcode 437. 路径总和 III

    题目描述: 给定一个二叉树,它的每个结点都存放着一个整数值. 找出路径和等于给定数值的路径总数. 路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点). 二 ...

随机推荐

  1. 如何在一个电脑上使用两个github账号

    问题描述:公司电脑上默认使用的是公司的github账号,如果希望写一些代码放到个人的github账号上,就需要配置让一个电脑上可以使用两个github账号 原理:管理两个SSH key 1.生成两个S ...

  2. word文档发布至博客wordpress网站系统

    今天ytkah接到一个需求:将word文档发布到wordpress网站上,因为客户那边习惯用word来编辑文章,想直接将内容导入到wp网站中,其实 Word 已经提供了这样的功能,并且能够保留 Wor ...

  3. 【UML】NO.71.EBook.9.UML.4.002-【PowerDesigner 16 从入门到精通】- RQM

    1.0.0 Summary Tittle:[UML]NO.71.EBook.9.UML.4.002-[PowerDesigner 16 从入门到精通]-  RQM Style:DesignPatter ...

  4. 实验隐藏参数"_allow_resetlogs_corruption"的使用

    实验环境:OEL 5.7 + Oracle 10.2.0.5 Tips:该参数仅在特殊恢复场景下使用,需要在专业Oracle工程师指导下进行操作. 1.隐藏参数说明 2.故障场景再现 3.非常规恢复 ...

  5. C++ const用法 尽可能使用const

    C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的.如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助. 1.c ...

  6. C#串口通信遇到的坑

    C#串口通信中有一个DataReceived事件可以委托一个接收函数.此接收函数是运行在辅线程(secondary thread)上的.如果要在这个函数中修改主线程中的一些元素,比如UI界面上的变量的 ...

  7. 自定义Word颜色主题

    外观 说明 看到这个黑色编辑器的界面,第一印象可能认为是Sublime.Atom. VScode或者其它markdown编辑器.其实仅仅是微软的Word经过了自定义主题. 选择清晰易于辨认的字体和深色 ...

  8. 面试(I)

    即时通讯 为什么要TCP连接建立3次? 假设是2次: 假如在第1次客户端向服务器端发送请求因为阻塞,客户端会再次给服务器端发送请求,这次服务器端和客户端建立了连接.这样双方就可以发送数据了,发送完以后 ...

  9. ORA-600 [kcblin_3] 解决方法

    今日,我们一个sql在某环境执行出错,如下: ORA-00600: 内部错误代码, 参数: [kcblin_3], [103], [253952], [8192], [32769], [312], [ ...

  10. VOC标签转化为YOLO标签

    参考darknet自带的voc_label.py import xml.etree.ElementTree as ET import pickle import os from os import l ...