算法通关村第十九关:白银挑战-动态规划高频问题

白银挑战-动态规划高频问题

1. 最少硬币数

LeetCode 322
https://leetcode.cn/problems/coin-change/description/

思路分析

尝试用回溯来实现
假如coins=[2,5,7],amount=27,求解过程中,每个位置都可以从[2,5,7]中选择,因此可以逐步将所有情况枚举出来,然后再找到要求的最少硬币数,图示如下:
在这里插入图片描述

通过上面的图,发现 f[20] 等已经存在多次重复计算了,存在大量重复计算的问题,效率低。

尝试用贪心来实现
直觉告诉我们尽量使用大的,假如coins=[2,5,7],amount=27
先连续用 7+7+7=21,剩下6用2+2+2=6,一共 7+7+7+2+2+2=27,共使用了6枚硬币
但我们可以使用 7+5+5+5+5=27,使用5枚硬币就够了
贪心的思路不能解决本题

尝试用DP来实现
使用DP能同时满足效率和准确性

设状态f(x),最少用f(x)枚硬币能拼出x
建立数组arr,索引表示的是 amount,表示最少需要arr[i]个硬币能拼出来
其中有些位放的是M,表示就是不能拼出来的意思

coins = [1,2,5] 时
在这里插入图片描述

coins = [2,5,7] 时
在这里插入图片描述

以coins=[2,5,7],详细分析一步步实现DP

第一步:确定状态和子问题

状态
f(x),最少用f(x)枚硬币能拼出x

子问题
先看最后一步,从后向前找递归

  • 最优策略K枚硬币,a1, a2, … ak 面值加起来是27
  • 除掉最后一枚硬币ak,前面硬币加起来面值就是 27-ak
  • ak一定是 2,5,7 中的一枚
    • 如果ak=2,f(27)=f(25)+1
    • 如果ak=5,f(27)=f(22)+1
    • 如果ak=7,f(27)=f(20)+1
  • f(27)=min(f(25),f(22),f(20))+1

在这里插入图片描述

f(27)=min(f(25),f(22),f(20))+1
接下来,根据递归的思想再去算 f(25),f(22),f(20)…

其中f(6)=min(f(4),f(1),f(-1))+1
f(1)和f(-1)不满足要求,需要设置为正无穷
f(4)=2,f(6)=2+1=3

总结:递推要从右向左,计算要从左向右

第二步:确定状态转移方程

子问题确定之后,状态转移方程也基本确定了
在这里插入图片描述

f(x) = min(f(x-2), f(x-5), f(x-7)) + 1

第三步:确定初始条件和边界

初始条件 f(0) = 0
如果不能拼出x,就定义f(x)为正无穷

注意溢出问题:
如果不能拼定义为正无穷,可能存在溢出问题
可以用amount来代替正无穷

第四步:按顺序计算

执行状态转移方程 f(x) = min(f(x-2), f(x-5), f(x-7)) + 1 对数组进行计算
递推要从右向左,计算要从左向右

第五步:代码实现

第一版代码

# 伪代码
def coin(coins, amount):max_num = amount + 1dp = [max_num] * (amount + 1)dp[0] = 0for i in range(amount + 1):if check(i):dp[i] = min(dp[i - coins[0]], dp[i - coins[1]], dp[i - coins[2]]) + 1return dp[amount] if dp[amount] < max_num else -1def check(i, coins):# 这里要保证 i - coins[i] 大于0# 这里还要保证不越界,写起来比较复杂,我们理解功能即可pass

第二版代码

# 伪代码
def coin(coins, amount):max_num = amount + 1dp = [max_num] * (amount + 1)dp[0] = 0for i in range(amount + 1):if check(i):dp[i] = min(dp[i], dp[i - coins[0]] + 1)dp[i] = min(dp[i], dp[i - coins[1]] + 1)dp[i] = min(dp[i], dp[i - coins[2]] + 1)return dp[amount] if dp[amount] < max_num else -1def check(i, coins):# 这里要保证 i - coins[i] 大于0# 这里还要保证不越界,写起来比较复杂,我们理解功能即可pass

如果 coins[] 数组比较大,if判断就非常长,考虑价格循环解决,最终代码实现如下

第三版代码

class Solution:def coinChange(self, coins: List[int], amount: int) -> int:max_num = amount + 1dp = [max_num] * (amount + 1)dp[0] = 0for i in range(1, amount + 1):for j in range(len(coins)):if i - coins[j] >= 0:dp[i] = min(dp[i], dp[i - coins[j]] + 1)return dp[amount] if dp[amount] < max_num else -1

总结一下求最值型DP的步骤

  1. 确定状态和子问题。
    从最后一步开始(最优策略中使用的最后一枚硬币ak)推导f(n)与子问题之间的关系,然后将其化成子问题(最少的硬币拼出更小的面值27-ak)

  2. 通过状态,可以得到状态转移方程 f(x) = min(f(x-2), f(x-5), f(x-7)) + 1

  3. 处理初始条件和边界问题。f[0]=0,其他如果不能拼出来标记为 f(x)=正无穷

  4. 从小到大开始计算。这里就是从 f(0),f(1),f(2)…向后计算

2. 最长连续递增子序列

LeetCode 674
https://leetcode.cn/problems/longest-continuous-increasing-subsequence/description/

思路分析

不用动态规划也能解决,例如前面介绍的滑动窗口就可以

动态规划解决

在这里插入图片描述

第一步:分析状态和子问题
状态 f(i) ,以a[i]结尾的最长连续上升子序列的长度

最后一个元素a[j],存在两种情况

  1. a[j]<=a[j-1],f[j]=1 (a[j]<=a[j-1] or j=0)
  2. a[j]>a[j-1],f[j] = f[j-1]+1 (j>0且a[j]>a[j-1])

第二步:转移方程
f[j]=1 a[j]<=a[j-1] or j=0)
f[j] = f[j-1]+1 j>0且a[j]>a[j-1]

第三步:初始条件和边界
f[j]=1 a[j]<=a[j-1] or j=0)
f[j] = f[j-1]+1 j>0且a[j]>a[j-1]

第四步:按照顺序计算
和硬币组合不一样,答案为 max([f(0), f(1), f(2), … ,f(n-1)])

代码实现

class Solution:def findLengthOfLCIS(self, nums: List[int]) -> int:# 动态规划arr = [1] * len(nums)for i in range(1, len(nums)):if nums[i] > nums[i-1]:arr[i] = arr[i-1] + 1return max(arr)

3. 最长递增子序列

LeetCode 300
https://leetcode.cn/problems/longest-increasing-subsequence/description/、

思路分析

本题与上一题 LeetCode 674 的区别:没有说子序列元素一定是连续的

在这里插入图片描述

使用DP解决问题的方法

第一步:确定状态和子问题
状态f(x) 第x个元素的最长递增子序列长度,求以a[x]结尾的最长上升子序列

子问题:
情况1:最长上升子序列为 { a[j] },f[j] = 1
情况2:最长上升子序列大于1
0<=i<j,a[j]>a[i],f[j] = f[i]+1
在这里插入图片描述

因为不确定最优策略中a[j]前一个元素a[i]是哪一个,需要枚举每个i,求以a[j]结尾的最长上升子序列
在这里插入图片描述

第二步:初始条件和边界
f[0] = 1
情况2必须满足
1.0<=i<j
2. a[j]>a[i]

第三步:计算顺序
计算 f[0],f[1],…,f[n-1]
答案就是这些数中最大的那个

代码实现

class Solution:def lengthOfLIS(self, nums: List[int]) -> int:# 动态规划arr = [1] * len(nums)for i in range(1, len(nums)):for j in range(i):if nums[i] > nums[j]:arr[i] = max(arr[j] + 1, arr[i])return max(arr)
class Solution:def lengthOfLIS(self, nums: List[int]) -> int:# 动态规划dp = []for i in range(len(nums)):dp.append(1)for j in range(i):if nums[i] > nums[j]:dp[i] = max(dp[j] + 1, dp[i])return max(dp)

4. 最少完全平方数

LeetCode279
https://leetcode.cn/problems/perfect-squares/description/

思路分析

手动画一下
在这里插入图片描述

使用DP来实现

第一步:确定状态和子问题
状态:f[i] i最少被分成几个完全平方数之和
子问题:参考硬币的问题,f[i] = min(f[i-j*j]+1, f[i]) 1<=j<i^0.5

第二步:状态转移方程
f[i] = min(f[i-j*j]+1, f[i]) 1<=j<i^0.5

第三步:初始和边界条件
f[0] = 0

第四步:计算顺序
计算 f[0],f[1],…,f[n-1],f[n]
答案就是f[n]

代码实现


class Solution:def numSquares(self, n: int) -> int:dp = [0]for i in range(1, n + 1):dp.append(i)for j in range(1, int(i**0.5) +1):dp[i] = (min(dp[i-j**2]+1, dp[i] ))return dp[n]

5. 再论青蛙跳

LeetCode 55
https://leetcode.cn/problems/jump-game/description/

思路分析
典型的贪心问题,下面从动态规划的角度来分析解题

  1. 状态和子问题
    状态:f[x] 能否跳跃值x
    子问题:可以跳到j,j可以跳到i
  2. 状态转移方程
    f[i] = True | 0<=j<i and f[j] and a[j]>=i-j
  3. 初始和边界条件
    f[0] = True
  4. 顺序
    计算 f[0],f[1],…,f[n-1] 答案就是f[n-1]

代码实现

class Solution:def canJump(self, nums: List[int]) -> bool:# 动态规划bp = [False] * len(nums)bp[0] = Truefor i in range(1, len(nums)):for j in range(i):if bp[j] and nums[j] >= i - j:bp[i] = Truereturn bp[-1]

注:动态规划可以实现,但在LeetCode上,运行超出时间限制

6. 解码问题

LeetCode 91
https://leetcode.cn/problems/decode-ways/description/

思路分析

  1. 状态和子问题
    状态:f[i] 以a[i]结尾,可以有多少种解码方式
    子问题:
    最后一个字母的解码有两种情况
    情况1:1个数字解码 a[i]对应一个字母,f[i] = f[i-1]
    情况2:2个数字解码 a[i-1]a[i]对应一个字母,f[i] = f[i-2]

    综上,f[i] = f[i-1](条件:a[i]可以对应一个字母) + f[i-2](条件:a[i-1]a[i]可以对应一个字母)

  2. 状态转移方程
    f[i] = f[i-1](条件:a[i]可以对应一个字母) + f[i-2](条件:a[i-1]a[i]可以对应一个字母)

  3. 初始和边界条件
    初始条件f[0] = 1 理解为空串,有一种解码方式
    边界条件:i = 1,只看最后一个数字就行了

  4. 顺序
    计算 f[0],f[1],…,f[n-1] 答案就是f[n]

代码实现

class Solution:def numDecodings(self, s: str) -> int:n = len(s)dp = [0] * (n + 1)dp[0] = 1for i in range(1, n + 1):if s[i - 1] != '0':dp[i] += dp[i - 1]if i > 1 and 10 <= int(s[i - 2]) * 10 + int(s[i - 1]) <= 26:dp[i] += dp[i - 2]return dp[len(s)]

7. 路径中存在障碍物

LeetCode 63
https://leetcode.cn/problems/unique-paths-ii/description/

我们在路径部分介绍了多种路径的问题
本专题有个重要的拓展,加入网格中存在障碍物怎么办?

本题是在 LeetCode 62 的基础上,如果中间某个位置存在障碍物,那一共有多少种路径。

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

在这里插入图片描述

思路分析

处理方法不算复杂
设置没有障碍物的格子标记为0,有障碍物的格子标记为1
执行的时候如果当前位置dp[i][j] == 1时,直接跳过

代码实现

class Solution:def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:m = len(obstacleGrid)n = len(obstacleGrid[0])bp = [[0] * n for _ in range(m)]bp[0][0] = 1 if obstacleGrid[0][0] == 0 else 0for i in range(m):for j in range(n):if obstacleGrid[i][j] == 1:bp[i][j] = 0elif i >= 1 and j >= 1:bp[i][j] = bp[i - 1][j] + bp[i][j - 1]elif i >= 1:bp[i][j] = bp[i - 1][j]elif j >= 1:bp[i][j] = bp[i][j - 1]return bp[-1][-1]
class Solution:def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:# 滚动数组实现m = len(obstacleGrid)n = len(obstacleGrid[0])dp = [0] * ndp[0] = 1 if obstacleGrid[0][0] == 0 else 0for i in range(m):for j in range(n):if obstacleGrid[i][j] == 1:dp[j] = 0elif j - 1 >= 0 and obstacleGrid[i][j - 1] == 0:dp[j] += dp[j - 1]return dp[n - 1]

8. 滚动数组技巧

LeetCode 118
https://leetcode.cn/problems/pascals-triangle/description/
LeetCode 119
https://leetcode.cn/problems/pascals-triangle-ii/description/

杨辉三角
特点:每个元素嗾使其二维矩阵中左上方和右上方的元素之和,是一种对称结构
在这里插入图片描述

用二维数组表示
在这里插入图片描述

  • 每一行的最右侧和最左侧都是1
  • 前两行时1,第3行才开始有累加的问题
  • 每一行的元素数量与其行数一样

代码实现

LeetCode 119

class Solution:def getRow(self, rowIndex: int) -> List[int]:# 新建二维数组a = [[] for _ in range(rowIndex+1)]for i in range(rowIndex+1):# 确定每一行数组a[i] = [0] * (i + 1)# 第1个和最后1个元素为1a[i][0] = 1a[i][i] = 1for i in range(rowIndex+1):# 从第3行开始if i >= 2:for j in range(1, len(a[i]) - 1):a[i][j] = a[i - 1][j - 1] + a[i - 1][j]return a[-1]

思路分析

如果要求只能使用O(n)空间实现呢?可以使用上面提到的滚动数组来做

a[i][j] = a[i-1][j-1] + a[i-1][j]

存在问题:计算dp[i]的时候,需要上一轮的dp[i-1],但是这个值已经被覆盖了
例如下图中的 3 需要使用上一轮的2和1进行相加,但是此时2已经覆盖成3了,所以仅靠一个一维数组无法解决问题

在这里插入图片描述

可以使用两个数组,也是O(n)空间,使用两个数组来进行轮换交流
如下图所示,黑色代表上一轮的结构,红色表示当前轮更新的结果
在这里插入图片描述

代码实现

LeetCode 119

class Solution:def getRow(self, rowIndex: int) -> List[int]:# 滚动数组,两个一维数组pre = []cur = []for i in range(rowIndex+1):for j in range(i + 1):if j == 0 or i == j:cur.append(1)else:cur.append(pre[j - 1] + pre[j])pre = curcur = []return pre

思路分析

如果只能使用一个一维数组来完成,该怎么做呢?

观察 cur[j] = pre[j-1] + pre[j],第 j 项的计算与上一行第 j-1 项和第 j 项有关

可以倒着算,就没有影响了,先计算第 j 项,再计算第 j-1 项,这样计算第 j 项时,第 j-1 项还是上一行的值
cur[j] = cur[j-1] + cur[j]

# 两个二维数组
for j in range(i + 1):if j == 0 or i == j:cur.append(1)else:cur.append(pre[j - 1] + pre[j])

变为

# 一个二维数组
for j in range(i, -1, -1):if j == 0 or i == j:arr[j] = 1else:arr[j] = arr[j - 1] + arr[j]

代码实现

LeetCode 119

class Solution:def getRow(self, rowIndex: int) -> List[int]:# 滚动数组,一个一维数组arr = []for i in range(rowIndex+1):arr.append(0)for j in range(i, -1, -1):if j == 0 or i == j:arr[j] = 1else:arr[j] = arr[j - 1] + arr[j]return arr

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/131430.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

error:Failed building wheel for XXX

解决方案适用于大多数的pip 安装时出现的Failed building wheel for XXX 出现问题 按以往快速安装包的经验&#xff0c;第一反应当然是使用简单又快捷的terminal命令加上镜像&#xff0c;如下&#xff1a; pip install torch -i https://pypi.tuna.tsinghua.edu.cn/simple结…

关闭训练过程中的wandb

训练的过程中反复提醒wandb的账户&#xff0c;自动化执行的话&#xff0c;不是很方便&#xff0c;因此需要关闭这个wandb的功能 提醒的方式是这样的&#xff1a; 解决办法1、注释掉wandb相关的代码&#xff0c;并且添加关闭命令&#xff1a;wandb None 参考&#xff1a; 训…

儿童折叠式和非折叠式椅子和凳子加拿大认证标准要求介绍 ASTM F2613-21

儿童折叠式和非折叠式椅子和凳子是带有刚性框架的座椅家具&#xff0c;用于支撑儿童的身体&#xff0c;使其可以直立或倾斜的姿势坐立或休息。 此次合规要求更新适用于儿童折叠式和非折叠式椅子和凳子。这类产品可以折叠起来&#xff0c;以便运输或储存。儿童椅是带有刚性框架的…

05JVM_类加载阶段

一、类加载阶段 1.加载 1.1介绍 ①Java源代码经编译生成字节码文件&#xff0c;通过类加载阶段将字节码载入方法区。 ②类加载阶段内部是C的instanceKlass描述java类&#xff0c;重要的域field有: _java_mirror&#xff0c;java类镜像。例如对String来说&#xff0c;就是St…

OpenCV(四十):图像分割—漫水填充

1.漫水填充原理 图像分割中的漫水填充&#xff08;Flood Fill&#xff09;算法是一种基于区域增长的像素分类方法。其原理是在图像中从种子点开始&#xff0c;逐渐向周围扩展&#xff0c;并根据一定的条件决定是否将相邻的像素归属于同一区域。 漫水填充的基本原理如下&#x…

通过数据模板自动生成表格table

1.数据模板中的主要几个参数需要注意下(需要加样式可自由设置参数)&#xff1a; title:填入表格的内容 col:1,占一列&#xff0c;row: 3&#xff0c;占3行 align:center居中对齐, pdL&#xff1a;14&#xff0c;padding-left:14, bold:true,加粗 width&#xff1a;100&#xff…

第十五届全国大学生数学竞赛报名快要截止了,你报上名了吗?

关于组织参加 第十五届全国大学生数学竞赛的通知 01 为了培养人才、服务教学、提高大学生学习数学的兴趣&#xff0c;培养学生分析问题、解决问题的能力&#xff0c;发现和选拔数学创新人才&#xff0c;为学生提供一个展示基础知识和思维能力的舞台&#xff0c;我校决定组织参…

02目标检测-传统检测方法

目录 一、目标学习的检测方法变迁及对比 二、 基于传统手工特征的检测算法的定义 三、传统主要手工特征与算法 Haar特征与 人脸检测算法 - Viola-Jones(了解) HOG特征与 SVM 算法(了解)&#xff08;行人检测、opencv实现&#xff09; SIFT特征与SIFT算法(了解) DPM&#…

[Java] String详解

愿一分耕耘,一份收获 文章目录 前言1. String基础概念2. String对象的比较2.1 与equals()的应用 3. 字符串的转化3.1 数字与字符串的转化3.2 大小写转换3.3 字符串与字符数组转换4. 字符串修改1.引入库2.读入数据 总结 前言 String这部分是面试中常常考到的题.string常量池,Sr…

java复习-线程的同步和死锁

线程的同步和死锁 同步问题引出 当多个线程访问同一资源时&#xff0c;会出现不同步问题。比如当票贩子A&#xff08;线程A&#xff09;已经通过了“判断”&#xff0c;但由于网络延迟&#xff0c;暂未修改票数的间隔时间内&#xff0c;票贩子B&#xff08;线程B&#xff09;…

带你打穿三层内网-红日靶场七

文章目录 前记环境配置web1信息搜集cve-2021-3129redis未授权|ssh密钥后渗透 Win7&#xff08;PC1&#xff09;永恒之蓝 web2docker逃逸 win7&#xff08;PC2&#xff09;|DC 前记 所用工具 msfcsvenomfrp蚁剑冰蝎laravel.pyfscan 注意事项 msf的永恒之蓝每次都需要两次才能…

Java 代理模式之静态代理与动态代理

1&#xff0c;代理模式 代理模式给某一个对象提供一个代理对象&#xff0c;并由代理对象控制对原对象的引用。通俗的来讲代理模式就是我们生活中常见的中介。 代理模式的目的&#xff1a; &#xff08;1&#xff09;通过引入代理对象的方式来间接访问目标对象&#xff0c;防…

geopandas 笔记:geometry上的操作汇总

如无特殊说明&#xff0c;数据主要来自&#xff1a;GeoDataFrame 应用&#xff1a;公园分布映射至subzone_UQI-LIUWJ的博客-CSDN博客 0 读入数据 subzone gpd.read_file(ura-mp19-subzone-no-sea-pl.geojson) subzone subzone_tstsubzone[0:5] subzone_tst subzone_tst.plot…

mingw 编译 curl ,Qt 工程使用

mingw 编译 curl 下载curl 源码 https://github.com/curl/curl 我使用8.3版 CMake-gui 配置 源码路径&#xff1a;D:/workspace/CPP/curl-8.3.0 生成路径: D:/workspace/CPP/curl-8.3.0/mingw-build 点击 Configure ,弹窗配置&#xff0c;选择 MinGW Makefiles 选择 Spec…

【Django】日志设置

原文作者&#xff1a;我辈李想 版权声明&#xff1a;文章原创&#xff0c;转载时请务必加上原文超链接、作者信息和本声明。 文章目录 LOGGING {version: 1,disable_existing_loggers: False,formatters: {verbose: {format: "[%(asctime)s] %(levelname)s [%(name)s:%(l…

探索AIGC人工智能(Midjourney篇)(四)

文章目录 Midjourney模特换装 Midjourney制作APP图标 Midjourney网页设计 Midjourney如何生成IP盲盒 Midjourney设计儿童节海报 Midjourney制作商用矢量插画 Midjourney设计徽章 Midjourney图片融合 Midjourney后缀参数 Midjourney模特换装 关键词生成模特照片 中国女性模特的…

Hadoop的第二个核心组件:MapReduce框架第一节

Hadoop的第二个核心组件&#xff1a;MapReduce框架第一节 一、基本概念二、MapReduce的分布式计算核心思想三、MapReduce程序在运行过程中三个核心进程四、如何编写MapReduce计算程序&#xff1a;&#xff08;编程步骤&#xff09;1、编写MapTask的计算逻辑2、编写ReduceTask的…

2023年数维杯数学建模A题河流-地下水系统水体污染研究求解全过程文档及程序

2023年数维杯数学建模 A题 河流-地下水系统水体污染研究 原题再现&#xff1a; 河流对地下水有着直接地影响&#xff0c;当河流补给地下水时&#xff0c;河流一旦被污染&#xff0c;容易导致地下水以及紧依河流分布的傍河水源地将受到不同程度的污染&#xff0c;这将严重影响…

SQL11 高级操作符练习(1)

描述 题目&#xff1a;现在运营想要找到男性且GPA在3.5以上(不包括3.5)的用户进行调研&#xff0c;请你取出相关数据。 示例&#xff1a;user_profile iddevice_idgenderageuniversitygpa12138male21北京大学3.423214male复旦大学4.036543female20北京大学3.242315female23浙…

C#__资源访问冲突和死锁问题

/// 线程的资源访问冲突&#xff1a;多个线程同时申请一个资源&#xff0c;造成读写错乱。 /// 解决方案&#xff1a;上锁&#xff0c;lock{执行的程序段}:同一时刻&#xff0c;只允许一个线程访问该程序段。 /// 死锁问题&#xff1a; /// 程序中的锁过多&#xf…