LeetCode 力扣 62. 不同路径

题目描述(中等难度)

机器人从左上角走到右下角,只能向右或者向下走。输出总共有多少种走法。

解法一 递归

求 ( 0 , 0 ) 点到( m - 1 , n - 1) 点的走法。

(0,0)点到(m - 1 , n - 1) 点的走法等于(0,0)点右边的点 (1,0)到(m - 1 , n - 1)的走法加上(0,0)点下边的点(0,1)到(m - 1 , n - 1)的走法。

而左边的点(1,0)点到(m - 1 , n - 1) 点的走法等于(2,0) 点到(m - 1 , n - 1)的走法加上(1,1)点到(m - 1 , n - 1)的走法。

下边的点(0,1)点到(m - 1 , n - 1) 点的走法等于(1,1)点到(m - 1 , n - 1)的走法加上(0,2)点到(m - 1 , n - 1)的走法。

然后一直递归下去,直到 (m - 1 , n - 1) 点到(m - 1 , n - 1) ,返回 1。

public int uniquePaths(int m, int n) {
    HashMap<String, Integer> visited = new HashMap<>();
    return getAns(0, 0, m - 1, n - 1, 0);

}

private int getAns(int x, int y, int m, int n, int num) {
    if (x == m && y == n) {
        return 1;
    }
    int n1 = 0;
    int n2 = 0;
    //向右探索的所有解
    if (x + 1 <= m) {
        n1 = getAns(x + 1, y, m, n, num);
    }
    //向左探索的所有解
    if (y + 1 <= n) {
        n2 = getAns(x, y + 1, m, n, num);
    }
    //加起来
    return n1 + n2;
}

时间复杂度:

空间复杂度:

遗憾的是,这个算法在 LeetCode 上超时了。我们可以优化下,问题出在当我们求点 (x,y)到(m - 1 , n - 1) 点的走法的时候,递归求了点 (x,y)点右边的点 (x + 1,0)到(m - 1 , n - 1)的走法和(x,y)下边的点(x,y + 1)到(m - 1 , n - 1)的走法。而没有考虑到(x + 1,0)到(m - 1 , n - 1)的走法和点(x,y + 1)到(m - 1 , n - 1)的走法是否是之前已经求过了。事实上,很多点求的时候后边的的点已经求过了,所以再进行递归是没有必要的。基于此,我们可以用 visited 保存已经求过的点。

public int uniquePaths(int m, int n) {
    HashMap<String, Integer> visited = new HashMap<>();
    return getAns(0, 0, m - 1, n - 1, 0, visited); 

}
private int getAns(int x, int y, int m, int n, int num, HashMap<String, Integer> visited) {
    if (x == m && y == n) {
        return 1;
    }
    int n1 = 0;
    int n2 = 0;
    String key = x + 1 + "@" + y;
    //判断当前点是否已经求过了
    if (!visited.containsKey(key)) {
        if (x + 1 <= m) {
            n1 = getAns(x + 1, y, m, n, num, visited);
        }
    } else {
        n1 = visited.get(key);
    }
    key = x + "@" + (y + 1);
    if (!visited.containsKey(key)) {
        if (y + 1 <= n) {
            n2 = getAns(x, y + 1, m, n, num, visited);
        }
    } else {
        n2 = visited.get(key);
    }
    //将当前点加入 visited 中
    key = x + "@" + y;
    visited.put(key, n1+n2);
    return n1 + n2;
}

时间复杂度:

空间复杂度:

解法二 动态规划

解法一是基于递归的,压栈浪费了很多时间。我们来分析一下,压栈的过程,然后我们其实完全可以省略压栈的过程,直接用迭代去实现。

如下图,如果是递归的话,根据上边的代码,从 (0,0)点向右压栈,向右压栈,到最右边后,就向下压栈,向下压栈,到最下边以后,就开始出栈。出栈过程就是橙色部分。

然后根据代码,继续压栈前一列,下图的橙色部分,然后到最下边后,然后开始出栈,根据它的右边的点和下边的点计算当前的点的走法。

接下来两步同理,压栈,出栈。

我们现在要做的就是要省略压栈的过程,直接出栈。很明显可以做到的,只需要初始化最后一列为 1 ,然后 1 列,1 列的向前更新就可以了。有一些动态规划的思想了。

public int uniquePaths(int m, int n) {
    int[] dp = new int[m];
    //初始化最后一列
    for (int i = 0; i < m; i++) {
        dp[i] = 1;
    }
    //从右向左更新所有列
    for (int i = n - 2; i >= 0; i--) {
        //最后一行永远是 1,所以从倒数第 2 行开始
        //从下向上更新所有行
        for (int j = m - 2; j >= 0; j--) {
            //右边的和下边的更新当前元素
            dp[j] = dp[j] + dp[j + 1];
        }
    }
    return dp[0];
}

时间复杂度:O(m * n)。

空间复杂度:O(m)。

这里也有一个类似的想法。不过他是正向考虑的,和上边的想法刚好相反。如果把 dp [ i ] [ j ] 表示为从点 (0,0)到点 ( i,j)的走法。

上边解法公式就是 dp [ i ] [ j ] = dp [ i + 1 ] [ j ] + dp [ i ] [ j +1 ]。

这里的话就是 dp [ i ] [ j ] = dp [ i - 1 ] [ j ] + dp [ i ] [ j - 1 ]。就是用它左边的和上边的更新,可以结合下图。

这样的话,就是从左向右,从上到下一行一行更新(当前也可以一列一列更新)。

public int uniquePaths(int m, int n) {
    int[] dp = new int[n];
    for (int i = 0; i < n; i++) {
        dp[i] = 1;
    }

    for (int i = 1; i < m; i++) {
        for (int j = 1; j < n; j++) {
            dp[j] = dp[j] + dp[j - 1];
        }
    }
    return dp[(n - 1)];
}

时间复杂度:O(m * n)。

空间复杂度:O(n)。

解法三 公式

参考这里

我们用 R 表示向右,D 表示向下,然后把所有路线写出来,就会发现神奇的事情了。

R R R D D

R R D D R

R D R D R

……

从左上角,到右下角,总会是 3 个 R,2 个 D,只是出现的顺序不一样。所以求解法,本质上是求了组合数,N = m + n - 2,也就是总共走的步数。 k = m - 1,也就是向下的步数,D 的个数。所以总共的解就是 C^k_n = n!/(k!(n-k)!) = (n*(n-1)*(n-2)*...(n-k+1))/k!

public int uniquePaths(int m, int n) {
    int N = n + m - 2; 
    int k = m - 1;  
    long res = 1; 
    for (int i = 1; i <= k; i++)
        res = res * (N - k + i) / i;
    return (int) res; 
}

时间复杂度:O(m)。

空间复杂度:O(1)。

从递归,到递归改迭代,之前的题也都遇到过了,本质上就是省去压栈的过程。解法三的公式法,直接到问题的本质,很厉害。

更多详细通俗题解详见 leetcode.wang

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

推荐阅读更多精彩内容

  • 前言 这是用来记录在刷LeetCode时遇到的一些问题的技巧,因只记录一些技巧或优化方案故不一定全部记录,自认为的...
    Cesarean阅读 822评论 0 0
  • "use strict";function _classCallCheck(e,t){if(!(e instanc...
    久些阅读 2,028评论 0 2
  • 动态规划 动态规划是一种高性能的牛逼算法,由美国的R.Bellman提出,它是动态就是体现在此算法是基于一个递推公...
    董泽平阅读 1,160评论 0 12
  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,325评论 0 2
  • 作为春节前的最后一周,这一周过的确实漫长啊~用度日如年来形容没有问题啊! 春节这个中华传统节日对于我们每个中国人来...
    周唐阅读 139评论 0 1