• 算法52-----矩阵最小路径【动态规划】


    一、题目:矩阵最小路径

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

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

    示例:

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

    思路1:时间O(M*N),空间O(M*N)

    新建一个矩阵dp(大小也是M*N),该矩阵是从上往下,从左往右记录每一步的结果的,当前的结果可以根据该矩阵上面和左边最小的值来获得,即:

    dp[i][j] = min(dp[i][j-1],dp[i-1][j]) + grid[i][j]

    如:

    grid =

    [ [1,3,1],
      [1,5,1],
      [4,2,1]]

    dp = 

    [[1,4,5],
      [2,7,6],
      [6,8,7]]

     

    所以结果为dp[-1][-1] = 7

    代码:

        def minPathSum(self, grid):
            """
            :type grid: List[List[int]]
            :rtype: int
            """
            #使用二维数组
            if not grid or not grid[0]:
                return 0
            if len(grid) <= 1:
                return sum(grid[0])
            dp = [[0]*len(grid[0]) for i in range(len(grid))]
            dp[0][0] = grid[0][0]
            for i in range(1,len(grid)):
                dp[i][0] = grid[i][0] + dp[i-1][0]
            for j in range(1,len(grid[0])):
                dp[0][j] = grid[0][j] + dp[0][j-1] 
            for i in range(1,len(grid)):
                for j in range(1,len(grid[0])):
                    dp[i][j] = min(dp[i][j-1],dp[i-1][j]) + grid[i][j]
            return dp[-1][-1]

    思路2:时间O(M*N),空间O( min(M,N) )

    新建一个列表dp(大小为min(M,N)),循环行数次更新记录每一行的路径值。

    如:

    grid =

    [ [1,3,1],
      [1,5,1],
      [4,2,1]]

    第一次更新:dp = [1,4,5]

    第二次更新:dp = [2,7,6],比如:原本dp =  [1,4,5],然后 先将 dp[0] 更新为2,然后dp[1] : 【min ( dp[0] 和dp [1] ) 与grid [1][1]相加之和】来更新 dp [1]

    第三次更新:dp = [6,8,7]

    更新是根据:

    dp[j] = min(dp[j-1],dp[j]) + grid[i][j]

    代码:

      #使用一维数组
            if not grid or not grid[0]:
                return 0
            if len(grid) <= 1:
                return sum(grid[0])
            if len(grid[0]) <= 1:
                return sum([val[0] for val in grid])
            n = min(len(grid),len(grid[0]))
            m = len(grid) if n == len(grid[0]) else len(grid[0])
            dp = [0] * n
            dp[0] = grid[0][0]
            if n == len(grid):
                grid = list(zip(*grid))
            for i in range(1,n):
                dp[i] = grid[0][i] + dp[i-1]
            for i in range(1,m):
                for j in range(n):
                    dp[j] = min(dp[j-1],dp[j]) + grid[i][j] if j>=1 else dp[j] + grid[i][j]
            return dp[-1]

    二、题目:地下城游戏

    例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7

    二、思路:动态规划:时间O(M*N),空间O(M*N)

    该题与最短路径相反。该题从最右下方开始,求起点。因为题目求的是初始血量,而最短路径求的是终点值,两者相反。

    dp[i][j]:如果骑士要走上位置(i,j),并且从该位置选一条最优的路径,最后走到右下角,骑士起码应具备的血量。最终结果为dp[0][0]。

     状态方程:

    • 初始化:
        • dp[m-1][n-1] = 1-dungeon[m-1][n-1] if dungeon[m-1][n-1] < 0 else 1
      •  for i in range(m-2,-1,-1):           

    dp[i][n-1] = max(dp[i+1][n-1] -  dungeon[i][n-1],1)       

      • for j in range(n-2,-1,-1):           

    dp[m-1][j] = max(dp[m-1][j+1] -  dungeon[m-1][j],1)

    • dp[i][j] 如何计算?     
    1. 如果骑士向右选择,dp[i][j]_1  = max{ dp[i][j+1] - map[i][j] , 1}     
    2. 如果骑士向下选择,dp[i][j]_2  = max{ dp[i+1][j] - map[i][j] , 1}     
    3. dp[i][j] = min{ dp[i][j]_1, dp[i][j]_2}

    代码:

    def minHP1(mat):
        if mat == None or mat[0] == None or len(mat) == 0 or len(mat[0]) == 0:
            return 1
        row = len(mat)
        col = len(mat[0])
        dp = [[0 for i in range(col)] for j in range(row)]
    #初始化 dp[row
    -1][col-1] = max(-mat[row-1][col-1]+1, 1) for i in range(row-2, -1, -1): dp[i][col-1] = max(dp[i+1][col-1] - mat[i][col-1], 1) for j in range(col-2, -1, -1): dp[row-1][j] = max(dp[row-1][j+1] - mat[row-1][j], 1)
    #更新dp[i][j]
    for i in range(row-2, -1, -1): for j in range(col-2, -1, -1): right = max(dp[i][j+1] - mat[i][j], 1) down = max(dp[i+1][j] - mat[i][j], 1) dp[i][j] = min(right, down) return dp[0][0] mat = [[-2,-3,3],[-5,-10,1],[10,30,-5]] minHP1(mat)

     


    三、题目:三角形最小路径和

    给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。

    例如,给定三角形:

    [
         [2],
        [3,4],
       [6,5,7],
      [4,1,8,3]
    ]
    

    自顶向下的最小路径和为 11(即,3 + 1 = 11)。

    思路:动态规划:时间O(M*N),空间O(M*N),类似矩阵最短路径,从上往下

    dp【i】【j】:表示第i行第j列时最短路径。

    子问题:邻近的两个:dp[i-1][j-1]、dp[i-1][j]

    状态方程:dp[i][j] = min(dp[i-1][j-1],dp[i-1][j]) + triangle[i][j]

    代码:

        def minimumTotal(self, triangle):
            """
            :type triangle: List[List[int]]
            :rtype: int
            """
            m = len(triangle)
            n = len(triangle[0])
            if not triangle or m == 0 or n == 0:
                return 0
            dp = [[triangle[0][0]]]
            for i in range(1,m):
                dp.append([0] * len(triangle[i]))
            # print(dp)
            for i in range(1,m):
                dp[i][0] = dp[i-1][0] + triangle[i][0]
                dp[i][-1] = dp[i-1][-1] + triangle[i][-1]
                for j in range(1,len(dp[i])-1):
                    dp[i][j] = min(dp[i-1][j-1],dp[i-1][j]) + triangle[i][j]
            return min(dp[-1])

    思路:动态规划:空间上优化成O (n) ,类似龙下城游戏,从下往上。

    状态方程:dp[j] = min(dp[j],dp[j+1]) + triangle[i][j]

    代码:

            #一维数组
            dp = triangle[-1]
            
            for i in range(m-2,-1,-1):
                for j in range(len(triangle[i])):
                    dp[j] = min(dp[j],dp[j+1]) + triangle[i][j]
            return dp[0]

    四、题目:下降路径最小和

    给定一个方形整数数组 A,我们想要得到通过 A下降路径最小和。

    下降路径可以从第一行中的任何元素开始,并从每一行中选择一个元素。在下一行选择的元素和当前行所选元素最多相隔一列。

    示例:

    输入:[[1,2,3],[4,5,6],[7,8,9]]
    输出:12
    解释:
    可能的下降路径有:
    
    • [1,4,7], [1,4,8], [1,5,7], [1,5,8], [1,5,9]
    • [2,4,7], [2,4,8], [2,5,7], [2,5,8], [2,5,9], [2,6,8], [2,6,9]
    • [3,5,7], [3,5,8], [3,5,9], [3,6,8], [3,6,9]

    和最小的下降路径是 [1,4,7],所以答案是 12

    提示:

    1. 1 <= A.length == A[0].length <= 100
    2. -100 <= A[i][j] <= 100

    代码:

        def minFallingPathSum(self, A):
            """
            :type A: List[List[int]]
            :rtype: int
            """
            if not A or len(A[0]) == 0:
                return 0
            m , n = len(A),len(A[0])
            dp = [[0] * n for i in range(m)]
            dp[0] = A[0]
            for i in range(1,m):
                for j in range(n):
                    if j==0:
                        dp[i][j] = min(dp[i-1][j+1],dp[i-1][j]) + A[i][j]
                    elif j == n-1:
                        dp[i][j] = min(dp[i-1][j-1],dp[i-1][j]) + A[i][j]
                    else:
                        dp[i][j] = min(dp[i-1][j-1],dp[i-1][j+1],dp[i-1][j]) + A[i][j]
            return min(dp[-1])

     五、有障碍的路径数量【不同路径II】

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

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

    现在考虑网格中有障碍物。那么从左上角到右下角将会有多少条不同的路径?

    网格中的障碍物和空位置分别用 10 来表示。

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

    示例 1:

    输入:
    [
      [0,0,0],
      [0,1,0],
      [0,0,0]
    ]
    输出: 2
    解释:
    3x3 网格的正中间有一个障碍物。
    从左上角到右下角一共有 2 条不同的路径:
    1. 向右 -> 向右 -> 向下 -> 向下
    2. 向下 -> 向下 -> 向右 -> 向右
    

     代码:

        def uniquePathsWithObstacles(self, obstacleGrid):
            """
            :type obstacleGrid: List[List[int]]
            :rtype: int
            """
            if not obstacleGrid or len(obstacleGrid[0]) == 0 or obstacleGrid[0][0] == 1:
                return 0
            m , n = len(obstacleGrid) , len(obstacleGrid[0])
            dp = [[0] * n for i in range(m)]
            dp[0][0] = 1 if obstacleGrid[0][0] != 1 else 0
            for i in range(1,m):
                if obstacleGrid[i][0] == 0 and dp[i-1][0] == 1:
                    dp[i][0] = 1
            for j in range(1,n):
                if obstacleGrid[0][j] == 0 and dp[0][j-1] == 1:
                    dp[0][j] = 1
            for i in range(1,m):
                for j in range(1,n):
                    if obstacleGrid[i][j] == 0:
                        dp[i][j] = dp[i-1][j] + dp[i][j-1]
            return dp[-1][-1]

     

    六、题目:出界的路径数:

    给定一个 m × n 的网格和一个球。球的起始坐标为 (i,j) ,你可以将球移到相邻的单元格内,或者往上、下、左、右四个方向上移动使球穿过网格边界。但是,你最多可以移动 次。找出可以将球移出边界的路径数量。答案可能非常大,返回 结果 mod 109 + 7 的值。

    示例 1:

    输入: m = 2, n = 2, N = 2, i = 0, j = 0
    输出: 6
    解释:
    

    示例 2:

    输入: m = 1, n = 3, N = 3, i = 0, j = 1
    输出: 12
    解释:
    

    说明:

    1. 球一旦出界,就不能再被移动回网格内。
    2. 网格的长度和高度在 [1,50] 的范围内。
    3. N 在 [0,50] 的范围内。

    思路:

    对于一个起始点为i,j,N步可以走出的点的路径个数,等于该点周围的4个点,N-1步可以走出的路径个数之和

    dp[k][i][j]表示起点在[i][j], 第k步可以走出路径个数之和。

    代码:

        def findPaths(self, m, n, N, i, j):
            """
            :type m: int
            :type n: int
            :type N: int
            :type i: int
            :type j: int
            :rtype: int
            """
            dp = [[[0] * n for y in range(m)] for k in range(N+1)]
            for k in range(1,N+1):
                for x in range(m):
                    for y in range(n):
                        n1 = dp[k-1][x-1][y] if x >= 1 else 1
                        n2 = dp[k-1][x][y-1] if y >= 1 else 1
                        n3 = dp[k-1][x+1][y] if x < m-1 else 1
                        n4 = dp[k-1][x][y+1] if y < n-1 else 1
                        dp[k][x][y] = (n1+n2+n3+n4)%(10**9 + 7)
            return dp[N][i][j]
     
  • 相关阅读:
    C语言宏中"#"和"##"的用法
    Ubuntu 14.04 LTS 安装和配置Bochs
    C和C++中static的比较
    总线设备驱动模型
    驱动设计的思想:面向对象/分层/分离
    基于分层思想的驱动程序软件框架
    单元测试指南
    Apollo移植
    剑指offer python版 数组中出现次数超过一半的数字
    剑指offer python版 字符串的排列
  • 原文地址:https://www.cnblogs.com/Lee-yl/p/9973770.html
Copyright © 2020-2023  润新知