首页 > 其他分享 >动态规划

动态规划

时间:2022-12-09 22:55:31浏览次数:37  
标签:初始化 return int range grid 动态 规划 dp

动态规划

讨论框架

# 自顶向下递归的动态规划
def dp(状态1, 状态2, ...):
    for 选择 in 所有可能的选择:
        # 此时的状态已经因为做了选择而改变
        result = 求最值(result, dp(状态1, 状态2, ...))
    return result

# 自底向上迭代的动态规划
# 初始化 base case
dp[0][0][...] = base case
# 进行状态转移
for 状态1 in 状态1的所有取值:
    for 状态2 in 状态2的所有取值:
        for ...
            dp[状态1][状态2][...] = 求最值(选择1,选择2...)

解题步骤

1.确定dp状态,即dp[]含义

2.确定状态转移方程

3.确定初始化状态

4.确定状态转移顺序

5.确定最优解

从刷题理解开始

斐波那契数列

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。

0 1 1 2 3 5 8 13 21 34 55

class Solution:
    def fib(self, n: int) -> int:
        if n < 2:
            return n
        dp=[0]*(n+1)
        dp[0]=0
        dp[1]=1
        for i in range(2,n+1):
          dp[i]=dp[i-1]+dp[i-2]
         return dp[n]

爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 :

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
class Solution:
    def climbStairs(self, n: int) -> int:
        # dp[i] 为第 i 阶楼梯有多少种方法爬到楼顶
        dp=[0]*(n+1)    #生成一个列表,n+1个,与下面循环一直
        dp[0]=1
        dp[1]=1
        for i in range(2,n+1):
            dp[i]=dp[i-1]+dp[i-2]
        return dp[n]
      
class Solution:
    def climbStairs(self, n: int) -> int:
        if n <= 1:
            return n
        # n 的取值范围 [1, 45],这里将 dp[0] = 0
        dp = [0] * (n + 1)
        dp[1] = 1
        dp[2] = 2
        for i in range(3, n + 1):
            dp[i] = dp[i - 1] + dp[i - 2]
        return dp[n]

易错点

dp是n+1个

最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 :

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。

- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
  总花费为 15 。
                               ____
                              │ o │
                      __20____|___|
               __15__│
        __10__│
__起点__│
class Solution:
    def climb(cost):
        n=len(cost)
        dp=[0]*(n+1)
        dp[0]=0
        dp[1]=0
        for i in range(2,n+1):
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
        return dp[n]

易错点

n需要自己计算获得

不同路径

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

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

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

img

  • 输入:m = 3, n = 7
  • 输出:28
class Solution:
    def uniquePaths(self, m: int, n: int) -> int:
        # 初始化 dp 数组,m 行 n 列
        dp = [[0] * n for _ in range(m)] 
      #这样写有一个坏处就是,m=n=1时 最后结果跑出来会是0,根据实际意义应该是1
      #有两种方案解决这个问题:①dp = [[1] * n for _ in range(m)] 默认设为1
             #             ②初始化之前加:if m==n==1:return 1
        
        
        # 初始化第 1 列
        for i in range(1, m):
            dp[i][0] = 1
        # 初始化第 1 行
        for i in range(1, n):
            dp[0][i] = 1
        # 状态转移方程
        for i in range(1, m):
            for j in range(1, n):
                dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[m - 1][n - 1]
      
a=Solution()
print(a.uniquePaths(1, 1))

易错点

原点的处理

不同路径2

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

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

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

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

img

输入:obstacleGrid = [[0,0,0],[0,1,0],[0,0,0]]
输出:2
解释:3x3 网格的正中间有一个障碍物。
从左上角到右下角一共有 2 条不同的路径:
1. 向右 -> 向右 -> 向下 -> 向下
2. 向下 -> 向下 -> 向右 -> 向右
class Solution:
    def uniquePathsWithObstacles(self, obstacleGrid) -> int:
        row = len(obstacleGrid)
        col = len(obstacleGrid[0])
        dp = [[0] * col for _ in range(row)]
        # 如果在起点或者终点有障碍物,返回 0
        if obstacleGrid[0][0] == 1 or obstacleGrid[row - 1][col - 1] == 1:
            return 0
        # 初始化行和列
        for i in range(col):  # 这里的指的是列,对行初始化,列在遍历 
            if obstacleGrid[0][i] == 0:
                dp[0][i] = 1
            else:
                break
        for j in range(row):
            if obstacleGrid[j][0] == 0:
                dp[j][0] = 1
            else:
                break

        for i in range(1, row):  # i代表的是行,取值是row
            for j in range(1, col):
                if obstacleGrid[i][j] == 0:
                    dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
        return dp[row - 1][col - 1]
              #或dp[-1][-1]

易错点

和最小路径和不同,初始化行列时,要从0开始,即for i in range(col),不是for i in range(1,col)

最小路径和

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

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

示例 :

img

输入:grid = [[1,3,1],[1,5,1],[4,2,1]]
输出:7
解释:因为路径 1→3→1→1→1 的总和最小。
class Solution:
    def minPathSum(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        dp = [[0] * n for _ in range(m)]
        #初始化起点,对于起点 (0,0) 来说,它的最小路径和就是 (0,0) 网格本身的值
        dp[0][0]=grid[0][0]
        # 初始化第一行
        for i in range(1,n):
            dp[0][i] = dp[0][i - 1] + grid[0][i]
        for i in range(1,m):
            dp[i][0] = dp[i - 1][0] + grid[i][0]

        for i in range(1,m):
            for j in range(1,n):
                dp[i][j] = min(dp[i - 1][j] , dp[i][j-1])+ grid[i][j]
        return dp[-1][-1]

易错点

dp = [[0] * col for _ in range(row)] 不要忘记0的中括号
初始化第一行、第一列索引是0
初始化起点
状态转移方程:不应该是dp[i][j] = min(dp[i - 1][j] + grid[i][j], dp[i][j-1]+ grid[i][j])
应该是dp[i][j] = min(dp[i - 1][j] , dp[i][j-1])+ grid[i][j]注意和最小花费爬楼梯的区别
所有的范围必须是从1开始,即range(1,m),千万注意range(m)从0开始取因为有dp[0][i-1]z这一项

image-20221209164508870

只有左边界,意味着该矩阵只有一列;只有上边界,意味着该矩阵只有一行;左边和上边都是边界,意味着该矩阵只有一个元素。

解法二

class Solution:
    def minPathSum(self, grid: [[int]]) -> int:
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if i == j == 0: continue
                elif i == 0:  grid[i][j] = grid[i][j - 1] + grid[i][j]
                elif j == 0:  grid[i][j] = grid[i - 1][j] + grid[i][j]
                else: grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]
        return grid[-1][-1]

整数拆分

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

image-20221209202040930
class Solution:
    def integerBreak(self, n: int) -> int:
      dp=[0]*(n+1)
      dp[2]=1
      
      for i in range(3,n+1):
        for j in range(1,i):
            # 对于正整数 i,拆分的第一个正整数是 j(1 ≤ j ≤ i - 1),存在两种情况:
            # 第 1 种:i 拆分成 j 和 i - j,且 i - j 不可再拆分,此时乘积为 i * (i - j)
            # 第 2 种:i 拆分成 j 和 i - j,且 i - j 可拆分,此时乘积是 i * dp[i- j]
        	dp[i]=max(dp[i],j*dp[i-j],j*(i-j))  
      return dp[n]

易错点

递归方程中,取大时,需要加入当前最大值dp[i]
比如在计算6✖️2 时,我不仅要比较j*dp[i-j],j*(i-j),我还要比较上面7✖️1的最优结果dp[i]

标签:初始化,return,int,range,grid,动态,规划,dp
From: https://www.cnblogs.com/happyfeliz/p/16970450.html

相关文章