动态规划专题 (I) :Leetcode 62 不同路径 + Leetcode 63 不同路径II + Leetcode 64 最小路径和

动态规划专题 (I) :Leetcode 62 不同路径 + Leetcode 63 不同路径II + Leetcode 64 最小路径和

写在前面:

很多人看到动规会很害怕,我之前也是。若是理解了动规是一个怎么样的过程,其实做起来也不是很难。

动规,在我看来实际上是分治的一种想法。可以见底下Leetcode 62题,他将一个大问题,分解为小问题,并从若干个个小问题中取最优的那个结果。而这个大问题分解成小问题的过程,被称之为:转移状态方程。

完成整个动规需要考虑两个因素:

  1. 如何确定边界点上的值(这一般直接取决于题意)
  2. 如何确定状态转移方程,这取决于题目内部的逻辑,是比较重要的一块。

Leetcode 62 不同路径

题目描述

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

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角(在下图中标记为“Finish”)。

问总共有多少条不同的路径?

在这里插入图片描述

第一反应:divide and conquer递归:

很容易可以看出,当前 m × n m\times n 网格的结果数等于 ( m 1 ) × n (m-1)\times n 个网格数下的结果,加上 m × ( n 1 ) m\times (n-1) 个网格数下的结果之和。然后讨论一下 m = 1 m = 1 n = 1 n = 1 的时候就可以了:

代码如下:

def uniquePaths_divide_and_conquer(m, n):
    def iterate(m, n):
        if (m == 1) | (n == 1):
            return 1
        if m == 2:
            return n
        if n == 2:
            return m
        if (m > 2) & (n > 2):
            return iterate(m-1, n)   iterate(m, n-1)
    return iterate(m, n)

特别简单,不知道为什么这道题说是中等难度。

改进

考虑到不断递归调用,有很多冗余的计算,那我考虑用一个矩阵来存储这些点:

def uniquePaths(m, n):
    if (m == 1) | (n == 1):
            return 1
    if (m == 2) | (n == 2):
        return max(m, n)
    # 初始化
    matrix = [None] * m
    for i in range(0, m):
        matrix[i] = [None] * n
    matrix[0][0] = 0
    for i in range(1, m):
        matrix[i][0] = 1
    for i in range(1, n):
        matrix[0][i] = 1
    # 开始递归实现:
    for i in range(1, m):
        for j in range(1, n):
            matrix[i][j] = matrix[i-1][j]   matrix[i][j-1]
    return matrix[-1][-1]

比如一个 3 × 7 3\times 7 的网格,他的矩阵长这样:

[ 0 1 1 1 2 3 1 3 6 1 4 10 1 5 15 1 6 21 1 7 28 ] \left[\begin{matrix} 0&1&1\\ 1&2&3\\ 1&3&6\\ 1&4&10\\ 1&5&15\\ 1&6&21\\ 1&7&28\\ \end{matrix}\right]
一个非常好的性质,就是从数值为 0 0 这个点到数值为 x x 的点一共有 x x 种不同的走法。(例如在一个 3 × 3 3\times3 的网格,一共有 6 6 种不同的走法)

因此最后输出位于右下角的元素即可。

提交结果

执行用时 :16 ms, 在所有 Python 提交中击败了88.64% 的用户

内存消耗 :11.7 MB, 在所有 Python 提交中击败了82.39%的用户

Leetcode 63 不同路径II

与上面一题不同的是,这题里面有障碍物,障碍物所在的地方是不能走的,那稍微复杂了一点点,只需要每步讨论的时候,考虑是否上方或左方以及自己本身是否有障碍物就可以了:

递推公式:

{ m a t r i x [ m , n ] = m a t r i x [ m 1 , n ] if 上方有障碍物 m a t r i x [ m , n ] = m a t r i x [ m , n 1 ] if 左方有障碍物 m a t r i x [ m , n ] = 0 if 上方和左方都有障碍物或自己本身就有障碍物 m a t r i x [ m , n ] = m a t r i x [ m , n 1 ] m a t r i x [ m 1 , n ] if 左方上方本身都没有障碍物 \begin{cases} matrix[m,n]=matrix[m-1,n]&\text{if 上方有障碍物}\\ matrix[m,n]=matrix[m,n-1]&\text{if 左方有障碍物}\\ matrix[m,n]=0&\text{if 上方和左方都有障碍物或自己本身就有障碍物}\\ matrix[m,n]=matrix[m,n-1] matrix[m-1,n]&\text{if 左方上方本身都没有障碍物}\\ \end{cases}

网站上有一个比较坑的例子导致第一次没过,就是起始点是有障碍物的情况。所以其实一开始直接讨论如果地图 ( 0 , 0 ) (0,0) 有障碍物直接输出 0 0 就可以了。

代码如下:

def uniquePathsWithObstacles(obstacleGrid):
    m = len(obstacleGrid)
    n = len(obstacleGrid[0])
    if obstacleGrid[0][0] == 1:
        return 0
    # 初始化
    matrix = [None] * m
    for i in range(0, m):
        matrix[i] = [None] * n
    for i in range(0, m):
        for j in range(0, n):
            if i == 0:
                if j == 0:
                    matrix[i][j] = 1
                else:
                    if (obstacleGrid[i][j] == 1) | (obstacleGrid[i][j-1] == 1):
                        matrix[i][j] = 0
                    else:
                        matrix[i][j] = matrix[i][j-1]
            elif (j == 0) & (i != 0):
                if (obstacleGrid[i-1][j] == 1) | (obstacleGrid[i][j] == 1):
                    matrix[i][j] = 0
                else:
                    matrix[i][j] = matrix[i-1][j]
            else:
                if (obstacleGrid[i][j] == 1) | (obstacleGrid[i-1][j]   obstacleGrid[i][j-1] == 2):
                    matrix[i][j] = 0
                else:
                    if obstacleGrid[i][j-1] == 1:
                        matrix[i][j] = matrix[i-1][j]
                    elif obstacleGrid[i-1][j] == 1:
                        matrix[i][j] = matrix[i][j-1]
                    else:
                        matrix[i][j] = matrix[i][j-1]   matrix[i-1][j]
return matrix[-1][-1]

如果有这样一个地图:

[ 0 0 1 0 0 1 0 0 1 0 0 0 0 0 0 ] \left[\begin{matrix} 0&0&1&0&0\\ 1&0&0&1&0\\ 0&0&0&0&0 \end{matrix}\right]

那么显然只有两种可能的路径:(红色与蓝色)

在这里插入图片描述

那么我们运行下来的递推矩阵是这样的:

在这里插入图片描述
跟我们预期的输出结果相同,因此这个算法是可行的。

提交结果:

执行时间:32ms

内存消耗:11.8MB
在这里插入图片描述

Leetcode 64 最小路径和

相似思路还有一道题:

题目描述:

给定一个包含非负整数的 m x n 网格,请找出一条从左上角到右下角的路径,使得路径上的数字总和为最小。

说明:每次只能向下或者向右移动一步。

示例:

输入:
[ [1,3,1],
[1,5,1],
[4,2,1]]
输出: 7
解释: 因为路径 1→3→1→1→1 的总和最小。

因为目标点都在边界上,则我们只需要求到前两个点所需的最短距离就可以了。整个递推式如下所示:

{ m a t r i x [ m , n ] = m a t r i x [ m 1 , n ] g r i d [ m , n ] if 在上边界点上 m a t r i x [ m , n ] = m a t r i x [ m , n 1 ] g r i d [ m , n ] if 在左边界点上 m a t r i x [ m , n ] = min ( m a t r i x [ m 1 , n ] m a t r i x [ m , n 1 ] ) g r i d [ m , n ] , otherwise \begin{cases} matrix[m,n]=matrix[m-1,n] grid[m,n]&\text{if 在上边界点上}\\ matrix[m,n]=matrix[m,n-1] grid[m,n]&\text{if 在左边界点上}\\ matrix[m,n]=\min(matrix[m-1,n]matrix[m,n-1]) grid[m,n],&\text{otherwise}\\ \end{cases}

这样取矩阵右下角,就是最短的路径:

代码如下:

def minPathSum(grid):
    m = len(grid)
    n = len(grid[0])
    # 初始化
    matrix = [None] * m
    for i in range(0, m):
        matrix[i] = [None] * n
    for i in range(0, m):
        for j in range(0, n):
            if i == 0:
                if j == 0:
                    matrix[i][j] = grid[i][j]
                else:
                    matrix[i][j] = matrix[i][j-1]   grid[i][j]
            elif j == 0:
                matrix[i][j] = matrix[i-1][j]   grid[i][j]
            else:
                matrix[i][j] = min(matrix[i-1][j], matrix[i][j-1])   grid[i][j]
        
    return matrix[-1][-1]

提交结果:

执行时间:75 ms

内存消耗:12.9 MB

在这里插入图片描述

发布了12 篇原创文章 · 获赞 6 · 访问量 612

猜你喜欢

转载自blog.csdn.net/weixin_44618103/article/details/104106727
今日推荐