当前位置: 首页 > news >正文

代码随想录第29天:动态规划2

一、不同路径(Leetcode 62)

  1. 确定dp数组以及下标的含义:从(0 ,0)出发,到(i, j) 有dp[i][j]条不同的路径
  2. 确定递推公式:想要求dp[i][j],只能从dp[i - 1][j] 和 dp[i][j - 1]两个方向推导出来。     
     dp[i][j] = dp[i - 1][j] + dp[i - 1][j]
  3. dp数组如何初始化:由于只能向右或向下走,所以dp[0][j] = 1, dp[i][0] = 1
  4. 确定遍历顺序:从前往后遍历
  5. 举例推导dp数组
class Solution:def uniquePaths(self, m: int, n: int) -> int:# 创建一个 m 行 n 列的二维 dp 数组,初始值都为 0dp = [[0]*n for _ in range(m)]# 第一列的所有位置都可以从上方到达,所以将其初始化为 1for i in range(m):dp[i][0] = 1# 第一行的所有位置都可以从左方到达,所以将其初始化为 1for j in range(n):dp[0][j] = 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]

二、不同路径II(Leetcode 63)

与路径I相比,这题引入了障碍设置。在上题的基础上添加限制,初始化时,第一行和第一列遇到障碍,则障碍后的格子都无法到达,直接break跳过;在第一行第一列的右下方出现障碍,则continue跳过。

class Solution:def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:# 获取网格的行数和列数m = len(obstacleGrid)n = len(obstacleGrid[0])# 创建一个大小为 m 行 n 列的 dp 数组,初始化为 0dp = [[0] * n for _ in range(m)]# 如果起点或终点有障碍物,直接返回 0,表示无法到达if obstacleGrid[m - 1][n - 1] == 1 or obstacleGrid[0][0] == 1:return 0# 初始化第一列,如果某个位置是障碍物,则后续位置都无法到达for i in range(m):if obstacleGrid[i][0] == 0:dp[i][0] = 1else:break  # 如果遇到障碍物,后面的所有位置都不能到达# 初始化第一行,如果某个位置是障碍物,则后续位置都无法到达for j in range(n):if obstacleGrid[0][j] == 0:dp[0][j] = 1else:break  # 如果遇到障碍物,后面的所有位置都不能到达# 通过动态规划计算每个位置的路径数for i in range(1, m):for j in range(1, n):# 如果当前格子有障碍物,则跳过if obstacleGrid[i][j] == 1:continue# 如果没有障碍物,则当前位置的路径数等于上方和左侧的路径数之和dp[i][j] = dp[i - 1][j] + dp[i][j - 1]# 返回右下角位置的路径数,即到达终点的路径数return dp[m - 1][n - 1]

三、整数拆分(Leetcode 343)

1.确定dp数组以及下标的含义:拆分数字i,可以得到的最大乘积为dp[i]

2.确定递推公式:

(1). dp[i] = j * (i - j), 拆分为2个数

(2). dp[i] = j * dp[i - j],拆分为3个以上的数

为什么不拆分 j 呢?

因为j是从1开始遍历,拆分j的情况,在遍历j的过程中其实都计算过。

递推公式:dp[i] = max(dp[i],  (i - j) * j,  dp[i - j] * j)); 

3.dp数组如何初始化:i = 0 和 1 无意义, dp[0]= 0, dp[1] = 0, dp[2] = 1

4.确定遍历顺序:从前往后遍历

5.举例推导dp数组

class Solution:def integerBreak(self, n: int) -> int:dp = [0] * (n + 1)dp[0], dp[1], dp[2] = 0, 0, 1  # 0 和 1 没有有效的拆分,2 只有拆分成 1 + 1 最大for i in range(3, n + 1):  # 从 3 开始,因为 1 和 2 已经初始化过for j in range(1, i // 2 + 1):  # j 从 1 到 i//2,避免重复计算dp[i] = max(dp[i], j * (i - j), j * dp[i - j])  # 计算最大乘积return dp[n]  # 返回最大乘积

细节优化:

由数学知识,当拆分的数字接近时积是最大的,最少拆分为2个数,这两个数分别为i//2 + 1和i//2 - 1,若拆分为3个以上数,拆分出的单个数字则会更小。所以 j 的遍历可以优化为:

 for j in range(1, i // 2 + 1):  # j 从 1 到 i//2,避免重复计算

四、不同的二叉搜索树(Leetcode 96)

1.确定dp数组以及下标的含义:dp[i] : 1到i为节点组成的二叉搜索树的个数为dp[i]。

也可以理解是i个不同元素节点组成的二叉搜索树的个数为dp[i] 

2.确定递推公式:以n = 3为例:

元素1为头结点搜索树的数量 = 右子树有2个元素的搜索树数量 * 左子树有0个元素的搜索树数量

元素2为头结点搜索树的数量 = 右子树有1个元素的搜索树数量 * 左子树有1个元素的搜索树数量

元素3为头结点搜索树的数量 = 右子树有0个元素的搜索树数量 * 左子树有2个元素的搜索树数量

有2个元素的搜索树数量就是dp[2]。

有1个元素的搜索树数量就是dp[1]。

有0个元素的搜索树数量就是dp[0]。

所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]

当n = i 时:

        dp[i] += dp[ 以j为头结点左子树节点数量 ] * dp[ 以j为头结点右子树节点数量 ]

        j相当于是头结点的元素,从1遍历到 i 为止。

        所以递推公式:dp[i] += dp[j - 1] * dp[i - j] ,j-1 为j为头结点左子树节点数量,i-j 为以j为头          结点右子树节点数量。

3.dp数组如何初始化:初始化,只需要初始化dp[0],初始化dp[0] = 1

4.确定遍历顺序:遍历i里面每一个数作为头结点的状态,用j来遍历。

5.举例推导dp数组

class Solution:def numTrees(self, n: int) -> int:# 初始化 dp 数组,其中 dp[i] 表示有 i 个节点的不同二叉搜索树的数量dp = [0] * (n + 1)dp[0], dp[1] = 1, 1  # 基础情况:0个节点和1个节点时都有1种构造方式# 从 2 到 n 计算每个节点数的不同二叉搜索树数量for i in range(2, n + 1):for j in range(1, i + 1):# 对于每个可能的根节点 j,左子树有 j-1 个节点,右子树有 i-j 个节点dp[i] += dp[j - 1] * dp[i - j]# 返回给定节点数 n 时的不同二叉搜索树数量return dp[n]
http://www.xdnf.cn/news/183727.html

相关文章:

  • Android ViewModel原理简要
  • 【算法笔记】贪心算法
  • Charles 抓包入门教程
  • 代码随想录算法训练营第60期第二十天打卡
  • 详细图解 Path-SAM2: Transfer SAM2 for digital pathology semantic segmentation
  • git每次push都要输入用户名和密码很繁琐,只在第一次输入之后都不需要的解决方法
  • 使用PHP对接印度股票市场数据
  • 睿享会丨走进西安御品轩
  • 代码随想录第28天:动态规划1
  • 每日c/c++题 备战蓝桥杯(P2392 kkksc03考前临时抱佛脚)
  • 若依/RuoYi 内置功能
  • tensor 的连续性 与 contiguous() 方法
  • 全星APQP软件系统:驱动芯片半导体行业研发管理迈向高效与合规新高度
  • 远程通信历史上为什么电话网络从模拟信号转向了数字信号?
  • Super Sample Tasker 学习-1
  • disruptor-spring-boot-start版本优化升级
  • LeetCode 每日一题 2025/4/21-2025/4/27
  • C++初阶-模板初阶
  • 杭电oj(1008、1012、1013、1014、1017)题解
  • 【文心快码】确实有点东西!
  • Redis 通用命令与keyspace
  • element-ui dropdown 组件源码分享
  • QML中的色彩应用
  • 调度算法的模拟及应用
  • 接口测试详解
  • electron-vite 应用打包自定义图标不显示问题
  • 28-29【动手学深度学习】批量归一化 + ResNet
  • Java线程池详解
  • 2024年12月GESP 图形化 一级考级真题——飞行的小猫
  • Linux的例行性工作(crontab)