62.不同路径

[TOC]

一、题目描述

一个机器人位于一个 m x n 网格的左上角 (起始点在下图中标记为“Start” )。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。
问总共有多少条不同的路径?

例如,上图是一个7 x 3 的网格。有多少可能的路径?


说明:m 和 n 的值均不超过 100。

示例 1:

输入: m = 3, n = 2
输出: 3

解释:

从左上角开始,总共有 3 条路径可以到达右下角。
1. 向右 -> 向右 -> 向下
2. 向右 -> 向下 -> 向右
3. 向下 -> 向右 -> 向右

示例 2:

输入: m = 7, n = 3
输出: 28

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/unique-paths
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

二、解题算法

1. 组合数


思路:

由于网格中并没有任何障碍物,那么机器人可以任意的向下或向右移动。而抵达目标一共需要走m+n-2步,其中n-1步向右,m-1步向下。
问有多少种路径,其实就是在问m+n-2步中,取n-1步向右,一共有多少种组合。当然你说取m-1步向下也是一样的。所以结果就变成了求解C^{n-1}_{n+m-2}或者C^{m-1}_{n+m-2}
但题目给出的数据m,n\in[0,100],直接使用组合数公式C^n_m=\frac{m!}{n!(m-n)!}暴力求解很容易导致计算过程中数据溢出。因而这里使用唯一分解定理将每一个因子转化为一串素数的乘积的形式,保证计算过程不会溢出(最终结果溢出不计入考虑范围)

复杂度分析的话,筛法部分超出能力范围完全不会计算,网上比较常见的说法是O(n\ ln\ {lnn})。但考虑到我这里直接计算到200,认为是常数时间O(1)应该是没有问题的。
在唯一分解部分,需要分解的一共是((n-1)-1+1)+((m+n-2)-m+1)总计2n-2个数字。
而单次唯一分解一共需要循环198次,每次循环最多的一次也不过logn次计算(这里的logn其实非常不严谨,但目前的水平只能分析到这个地步了)。
因而总时间复杂度的一个上界应该是O(nlogn),再严格的分析就超出我能力了,若有大佬会算,还望不吝赐教。

PS:可以利用mn等价,将复杂度变为O(min(m,n)*log(min(m,n)))


代码:

class Solution {
public:
    int uniquePaths(int m, int n) {
        if (m == 0 || n == 0)
            return 0;
        
        // 将200以下所有的素数统计出来,方便后续计算
        // 因为m+n-2最大为198,所以统计到200绰绰有余
        vector<int> is_prime(200, 1);
        cal_prime(is_prime);
        
        vector<int> factors(is_prime.size(), 0);
        for (auto i = 1; i <= n - 1; ++i)
            decomposition(factors, is_prime, i, -1);
        for (auto i = m; i <= m + n - 2; ++i)
            decomposition(factors, is_prime, i, 1);
        
        auto ans = 1;
        for (auto i = 2u; i < factors.size(); ++i)
            // 组合数一定是整数,所以不会出现负指数的素因子
            if (factors[i] > 0)
                ans *= std::pow(i, factors[i]);
        return ans;
    }
        
    // 筛法求解素数
    void cal_prime(vector<int>& is_prime) {
        for (auto i = 2u; i < is_prime.size(); ++i)
            if (is_prime[i])
                for (auto j = i + i; j < is_prime.size(); j += i)
                    is_prime[j] = 0;
    }
    
    // 分解每个因子
    void decomposition(vector<int>& factors, vector<int>& is_prime, int num, int sign) {
        for (auto i = 2u; i < is_prime.size(); ++i) {
            if (is_prime[i])
                while (num % i == 0) {
                    num /= i;
                    factors[i] += sign;
                }
        }
    }
};

2. 暴力法


思路:

直接通过DFS暴力枚举所有路径,然后统计出能成功抵达重点的路径即可。
一个m*n个网格,每个网格都有向下和向右两种可能的行走方案,因而时间复杂度O(2^{mn})
显然的,超时,并且当m=15,n=15时便已经无法通过测试案例。


代码:

class Solution {
public:
    int uniquePaths(int m, int n) {
        ans = 0;
        this->m = m;
        this->n = n;
        dfs(0, 0);
        return ans;
    }

    void dfs(int r, int c) {
        if (r == m - 1 && c == n - 1)
            ans++;
        for (auto i = 0; i < 2; ++i)
            if (r + row[i] < m &&
                c + col[i] < n)
                dfs(r + row[i], c + col[i]);
    }

private:
    int row[2] = { 0, 1 };
    int col[2] = { 1, 0 };
    int m, n, ans;
};

3. 动态规划


思路:

解法1太过讨巧,求组合数其他情况未必适用。解法2太过简单粗暴,它模拟了整个行走的过程。
那有没有办法折中一下?速度上可以慢于解法1,但不用模拟行走过程,以达到比解法2更快的效果?

想要不模拟行走过程,那我们程序中应该记录什么呢?
回想一下,我们模拟行走过程,是为了从所有行走路线中,找出可以抵达终点的有效路线。也就是说,行走并不是我们关注的重点,计数才是。

那为何不试试直接计数呢?如果可以通过某种手法直接计算出到达每一个网格所需的步数,然后直接返回到终点网格所需要的步数不就好了?
说干就干!
首先缩小问题规模,如果整个网格只有一行,那么答案应该是什么呢?显然,由于只有一行,那么机器人每一步都只能往右走,因而可以到达该行中每个网格的路线应该都只有1条,即dp[i] = 1,\ i\in [1, n]。那么,如果整个网络只有一列呢?同理,到达该列中的每个网格的路线也都应该只有一条,即dp[j]=1,\ j\in[1, m]
好了,最简单的情况考虑过了,接下来要考虑的是,对于一个普通的网格,抵达每一格的路线一共有多少种呢?
仔细思考一下,对任意的网格,抵达它有两种方式,一种是它的从左侧,另一种是从它的上方,分别对应了机器人的向右走和向下走。所以很明显,抵达它的路径数为抵达它上方网格的路径数与抵达它左侧网格的路径数之和。
记抵达第ij列的网格的路径数量为dp[i][j],可得
\begin{equation} dp[i][j]= \begin{cases} 1,& i=1\ \text{or}\ j=1\\ dp[i-1][j]+dp[i][j-1],& i\in[2,m]\ \text{and}\ j\in[2,n] \end{cases} \end{equation}
由于每个网格只计算了一次,且记录了每一个网格的路径数,因而时间复杂度O(mn),空间复杂度O(mn)


代码:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> dp(m);
        for (auto i = 0; i < m; ++i)
            dp[i].resize(n, 1);
        for (auto i = 1; i < m; ++i)
            for (auto j = 1; j < n; ++j)
                dp[i][j] = dp[i-1][j] + dp[i][j-1];
        return dp.back().back();
    }
};

4. 动态规划(内存优化)


思路:

其实仔细观察一下解法3的代码,我们会发现dp过程中只用得到dp[i-1]dp[i]这两行。而dp[i]是在dp[i-1]的基础上进行的计算,所以其实我们能将dp数组从二维降到一维,将空间复杂度由O(mn)优化到O(n)


代码:

class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<int> dp(n, 1);
        for (auto i = 1; i < m; ++i)
            for (auto j = 1; j < n; ++j)
                dp[j] = dp[j] + dp[j-1];
        return dp.back();
    }
};
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,445评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,889评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,047评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,760评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,745评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,638评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,011评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,669评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,923评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,655评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,740评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,406评论 4 320
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,995评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,961评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,197评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,023评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,483评论 2 342

推荐阅读更多精彩内容