算法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]

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM