zoukankan      html  css  js  c++  java
  • LeetCode 62,从动态规划想到更好的解法

    本文始发于个人公众号:TechFlow,原创不易,求个关注


    今天是LeetCode专题第36篇文章,我们一起来看下LeetCode的62题,Unique Paths。

    题意

    其实这是一道老掉牙的题目了,我在高中信息竞赛的选拔考试上就见过这题。可想而知它有多古老,或者说多经典吧。一般来说能够流传几十年的算法题,一定是经典中的经典。下面我们就来看下它的题意。

    这题的题意很简单,给定一个矩形的迷宫,左上角有一个机器人,右下角是目的地。这个机器人只能向下走或者是向右走,请问这个机器人走到目的地的路径一共有多少种?

    这题很良心地给定了条件,矩形的长和宽都不超过100.

    样例

    Input: m = 3, n = 2
    Output: 3
    Explanation:
    From the top-left corner, there are a total of 3 ways to reach the bottom-right corner:
    1. Right -> Right -> Down
    2. Right -> Down -> Right
    3. Down -> Right -> Right
    
    Input: m = 7, n = 3
    Output: 28
    

    解法

    在我写题解的时候,我突然想起来上一次见到它好像是在某个综艺节目当中。它被作为一道智力题来考一些明星嘉宾,好像一众明星里面,只有关晓彤做了出来。。。

    它作为智力题有一个标准的模板式解法:

    对于图中的C点来说,从起点通往它的路径数量等于通往A点和B点路径的和。

    这个结论我们很多人都知道,因为C点只有两个来源,一个是A点一个是B点。它既可以从A点来,也可以从B点来,所以应该是一个加和的关系。

    这当然是没错的,但不知道大家从这个过程当中有没有什么感悟。C点的上游是A点和B点,也就是说C状态是由A状态或者是B状态转移到的。这不就是一个动态规划算法吗?

    我们用dp记录每一个位置的答案的话,那么可以很轻松地写出状态转移方程:

    dp[i][j] = dp[i-1][j] + dp[i][j-1]
    

    我们用代码把这个方程实现就能解出问题了。

    class Solution:
        def uniquePaths(self, m: int, n: int) -> int:
            dp = [[0 for _ in range(n+2)] for _ in range(m+2)]
            
            dp[0][1] = 1
            
            for i in range(1, m+1):
                # 特殊处理第一列,因为第一列只有1种
                dp[i][1] = dp[i-1][1]
                for j in range(2, n+1):
                    dp[i][j] = dp[i-1][j] + dp[i][j-1]
                    
            return dp[m][n]
    

    one more solution

    如果只有上面这一种解法,那么这道题完全没有说的必要,也太简单了。这道题还有另外一种解法,会更加简单

    在上面的解法当中,我们是把这个问题当成了算法题来解决的,使用动态规划算法进行建模,适配题目来解决它。但如果我们换个思路,完全可以把它当做数学问题来解。

    我们来分析一下问题,机器人要从左上角走到右下角,地图是没有缺陷的,所有点都可以到达。由于机器人没办法走回头路,也就是说机器人在通往终点的过程当中走过的路程是确定的。也就是要走n-1条横边和m-1条竖边。

    边的总数和种类都确定了,其实这个问题可以转化一下。我们把走的横边看成是白球,走的竖边看成是黑球,那么这道题其实就可以转化成,我们有n-1个白球,m-1个黑球,现在把它们排成一排,一共有多少种方法?

    这个是小学的组合数学问题,我们要从整体的n+m-2个物体当中,选出n-1个,那么显然答案就是:

    不过,虽然我们用一个式子就表达了,但是要求解这个组合数,还是需要通过循环的。我们把它转化成:

    接着我们用循环求解即可。

    class Solution:
        def uniquePaths(self, m: int, n: int) -> int:
            ret = 1
            
            for i in range(1, n):
                ret = ret * (n+m-1-i) // i
            return ret
    

    相比于上面的做法更加简短,虽然两者看起来都是的算法,好像差别不大。但是每个数的阶乘和组合数都是可以预处理的,在频繁求解的场景下,显然要比动态规划算法更快。

    结尾

    这道题就算是讲完了,大家会发现LeetCode过了50题之后,涉及到新的算法明显变少了。毕竟LeetCode并不是高强度的算法平台,主打的还是算法入门。这也限制了它的难度无法和codeforces、topcoder这种主打算法竞赛的平台相比。所以很多人刷到一百题左右的时候,就会觉得没意思。

    感觉很多题目大同小异,新鲜感和正反馈都变少了。这个时候就需要我们把问题做精,多去思考和求解最完美的解法。即使是为了应付面试而刷题,这也是很有必要的。高水平的面试官往往都不会出LeetCode上的原题,都会加一些变化来考察选手,甚至有些大牛会出原创题。光摸清套路是不够的,我们还需要更加深入的理解。

    套用一句电影台词作为结尾:we need to go deeper.

    今天的文章就到这里,看官大人,请关注我吧

  • 相关阅读:
    转物理老师的说说,过好自己的生活,别人的梦幻生活背后也有你看不见的无奈
    第一、二、三、BC范式的学习总结
    一位毕业四年的前辈的经历
    普里姆算法,克鲁斯卡尔算法,迪杰斯特拉算法,弗洛里德算法
    从零开始构建JavaScript框架4——DOM遍历2
    从零开始构建JavaScript框架3——DOM遍历1
    从零开始构建JavaScript框架2——总体结构以及元素的获取和创建
    从零开始构建JavaScript框架1——为何JavaScript开发需要框架
    从浏览器输入URL到页面加载完成到底发生了什么?
    第5课 C语言指针深入1
  • 原文地址:https://www.cnblogs.com/techflow/p/12872847.html
Copyright © 2011-2022 走看看