动态规划 典型例题

总结

动态规划的的四个解题步骤是:

  • 定义子问题
  • 写出子问题的递推关系
  • 确定 DP 数组的计算顺序
  • 空间优化(可选)
from functools import cache
@cache #缓存,避免重复运算
def dfs(i)->int:if 终止: return 0 #具体返回什么值要看题目的含义cnt = 0for 递归方向:cnt += dfs(xxx) #如果是计数,一般是叠加,也有可能是取最大或者最小return cnt

509 F 数列

注意:

f0 = 0, f1 = 0, f2 = 1

class Solution:def fib(self, n: int) -> int:if n < 2:return np, q, r = 0, 0, 1for i in range(2, n + 1):p, q = q, rr = p + qreturn r

198 打家劫舍

class Solution:def rob(self, nums: List[int]) -> int:N = len(nums)dp = [0] * (N + 1)# dp[i]  [0, i) 家里面能偷得的最大金额dp[0] = 0dp[1] = nums[0]for k in range(2, N + 1):dp[k] = max(dp[k - 1], nums[k - 1] + dp[k - 2]);return dp[N]

64 最小路径和

class Solution:def minPathSum(self, grid: List[List[int]]) -> int:if not grid or not grid[0]:return 0rows, columns = len(grid), len(grid[0])# 注意可以这样初始化dp = [[0] * columns for _ in range(rows)]dp[0][0] = grid[0][0]for i in range(1, rows):dp[i][0] = dp[i - 1][0] + grid[i][0]for j in range(1, columns):dp[0][j] = dp[0][j - 1] + grid[0][j]for i in range(1, rows):for j in range(1, columns):dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]return dp[rows - 1][columns - 1]

62 不同路径

class Solution:def uniquePaths(self, m: int, n: int) -> int:dp = [[1] * n] + [[1] + [0] * (n - 1) for _ in range(m - 1)]for i in range(1, m):for j in range(1, n):f[i][j] = f[i - 1][j] + f[i][j - 1]return f[m - 1][n - 1]

63 有障碍物的不同路径

class Solution:def uniquePathsWithObstacles(self, obstacleGrid):m = len(obstacleGrid)n = len(obstacleGrid[0])dp = [[0] * n for _ in range(m)]if obstacleGrid[0][0] != 1:dp[0][0] = 1# 给第一行赋值for i in range(1, n):if obstacleGrid[0][i] != 1:dp[0][i] = dp[0][i - 1]# 给第一列赋值for j in range(1, m):if obstacleGrid[j][0] != 1:dp[j][0] = dp[j - 1][0]for i in range(1, m):for j in range(1, n):# 关键判断if obstacleGrid[i][j] != 1:dp[i][j] = dp[i - 1][j] + dp[i][j - 1]return dp[m - 1][n - 1]

118 杨辉三角

行号从 1 开始,列从 0 开始

class Solution:def generate(self, numRows):# f[i][j]: 第 i 行第 j 个元素的值# f[i][j] = f[i - 1][j - 1] + f[i - 1][j]# 注意给边界赋值dp = []dp.append([1])# numRows > 1for i in range(1, numRows):dp.append([0] * (i + 1))for j in range(i + 1):if j == 0 or j == i:dp[i][j] = 1else:dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j]return dp

120 杨辉三角的变体

class Solution:def minimumTotal(self, triangle):# f[i][j] : 到达第 i 行 j 个元素的最小路径# f = min(f[i - 1][j - 1], f[i - 1][j]) + triangle[i][j]# 边界只能累加m = len(triangle)dp = [triangle[0]]# 类似杨辉# 行列都是从 0 开始计数for i in range(1, m):dp.append([0] * (i + 1))for j in range(i + 1):if j == 0:dp[i][j] = dp[i - 1][j] + triangle[i][j]elif j == i:dp[i][j] = dp[i - 1][j - 1] + triangle[i][j]else:dp[i][j] = min(dp[i - 1][j - 1], dp[i - 1][j]) + triangle[i][j]# 找到最下面一行的最小值ans = float('inf')for i in range(m):ans = min(ans, dp[m - 1][i])return ans

279 完全平方数

import mathclass Solution:def numSquares(self, n: int) -> int:"""求 f(n): 和为 n 的完全平方数的最少数量 f(n) = f(n - j ^ 2) + 1j^2 <= nf(1) = 1f(2) = f(2 - 1) + 1 = 2f(3) = min(f(3 - 1)) + 1 = 3;f(4) = f(0) + 1 or f(3) + 1"""# 初始化动态规划数组,储存和为 i 的最少完全平方数数量# 数值范围 1 -- ndp = [0] * (n + 1)dp[1] = 1  # 初始条件,和为 1 的最少数量为 1# 遍历计算从 2 到 n 的和为 i 的最少完全平方数数量for i in range(2, n + 1):s = int(math.sqrt(i))  # 计算 i 的平方根,作为循环的上界ans = float('inf')  # 初始化 ans 为正无穷,用于记录最小数量for j in range(s, 0, -1):ans = min(ans, dp[i - j * j] + 1)  # 更新最小数量dp[i] = ans  # 记录和为 i 的最小数量return dp[n]  # 返回和为 n 的最小完全平方数数量

377 组合总和

不需要去重

class Solution:def combinationSum4(self, nums, target):"""f[i] = nums 中可以组合为 i 的元素组合的个数f[i] = Σf[i - nums[j]]初始化为1"""dp = [0] * (target + 1)dp[0] = 1for i in range(1, target + 1):for num in nums:if num <= i and dp[i] < float('inf') - dp[i - num]:dp[i] += dp[i - num]return dp[target]

300 最长递增子序列*

以下标 i 的元素结尾的最长递增子序列的长度

class Solution:def lengthOfLIS(self, nums):"""dp[i] 以下标为 i 的数结尾的最长递增子序列的长度"""dp = [1] * len(nums)for i in range(1, len(nums)):for j in range(i):if nums[j] < nums[i]:dp[i] = max(dp[i], dp[j] + 1)return max(dp)

674 最长连续递增子序列*

class Solution:def findLengthOfLCIS(self, nums: List[int]) -> int:dp = [0] * len(nums)ans = 1for i in range(1, len(nums)):if nums[i] > nums[i - 1]:dp[i] = dp[i - 1] + 1ans = max(ans, dp[i])return ans

718 最长公共子串

class Solution:def findLength(self, nums1, nums2):"""f(i, j) 包含 nums1 的第 i 个数字和包含 nums2 的第 j 个数字的最长重复子数组的长度if nums1[i] == nums2[j]f(i, j) = f(i - 1, j - 1) + 1else:f(i, j) = 0"""# 第一行和第一列表示不取任何数字dp = [[0] * (len(nums2) + 1) for _ in range(len(nums1) + 1)]ans = 0# 遍历行for i in range(1, len(nums1) + 1):# 遍历列for j in range(1, len(nums2) + 1):if nums1[i - 1] == nums2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1# 肯定不是最长公共子串else:dp[i][j] = 0ans = max(ans, dp[i][j])return ans

1143 最长公共子序列

可以不连续,所以else后面有差异

class Solution:def longestCommonSubsequence(self, text1: str, text2: str) -> int:len1 = len(text1)len2 = len(text2)dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]for i in range(1, len1 + 1):for j in range(1, len2 + 1):if text1[i - 1] == text2[j - 1]:dp[i][j] = dp[i - 1][j - 1] + 1else:dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])return dp[len1][len2]

583 两个字符串的删除操作

class Solution:def minDistance(self, word1, word2):"""f(i, j) 将 s1 的前 i 个字符和 s2 的前 j 个字符变为相同的最小操作数if s1[i] == s2[j]f[i - 1, j - 1]elsemin (f[i - 1, j], f[i, j - 1]) + 1f[0, j] = jf[i, 0] = i"""len1 = len(word1)len2 = len(word2)dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]for j in range(len2 + 1):dp[0][j] = jfor i in range(len1 + 1):dp[i][0] = ifor i in range(1, len1 + 1):for j in range(1, len2 + 1):if word1[i - 1] == word2[j - 1]:dp[i][j] = dp[i - 1][j - 1]else:dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + 1return dp[len1][len2]

72 编辑距离

大体和上一题类似,操作有三种

class Solution:def minDistance(self, word1: str, word2: str) -> int:"""f(i, j) : word1 的前 i 个转到 word2 的前 j 个插入f(i, j) = f(i, j - 1) + 1删除f(i, j) = f(i - 1, j) + 1替换f(i, j) = f(i - 1, j - 1) + 1相等f(i, j) = f(i - 1, j - 1)f(0, j) = jf(i, 0) = i"""len1 = len(word1)len2 = len(word2)dp = [[0] * (len2 + 1) for _ in range(len1 + 1)]for i in range(len1 + 1):dp[i][0] = ifor i in range(len2 + 1):dp[0][i] = ifor i in range(1, len1 + 1):for j in range(1, len2 + 1):if word1[i - 1] == word2[j - 1]:dp[i][j] = dp[i - 1][j - 1]else:dp[i][j] = min(min(dp[i][j - 1], dp[i - 1][j]), dp[i - 1][j - 1]) + 1return dp[len1][len2]

647 回文子串

考虑子串长度

1个

2个

3个及以上

class Solution:def countSubstrings(self, s: str) -> int:"""f(i, j) i 到 j 的子串是否为回文串 0 -- n - 11. s[i] = s[j] && f(i + 1, j - 1)i + 1 <= j - 1 ==> i + 2 <= j2. 单独算 i + 1 = j, i = j"""n = len(s)cnt = 0dp = [[False] * n for _ in range(n)]for j in range(n):for i in range(j, -1, -1):# base case# 只有一个字符if i == j:dp[i][j] = Trueelif i + 1 == j:dp[i][j] = (s[i] == s[j])else:dp[i][j] = s[i] == s[j] and dp[i + 1][j - 1]if dp[i][j] == True:cnt += 1return cnt

516 最长回文子序列

class Solution:def longestPalindromeSubseq(self, s: str) -> int:len_s = len(s)dp = [[0] * len_s for _ in range(len_s)]for j in range(len_s):for i in range(j, -1, -1):if i == j:dp[i][j] = 1elif i + 1 == j:dp[i][j] = 2 if s[i] == s[j] else 1else:if s[i] == s[j]:dp[i][j] = dp[i + 1][j - 1] + 2else:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1])return dp[0][len_s - 1]

416 0-1 背包 分割等和子集

class Solution:def canPartition(self, nums: List[int]) -> bool:# 求和total_sum = sum(nums)if total_sum % 2 == 1:return Falsetarget = total_sum // 2# 能否凑出半个 0-1 背包# 考虑前 i 个数字能否凑出 j# f(i, j) = f(i - 1, j) or f(i - 1, j - nums[i])# base case: f(0, j): f(0, 0) = Truedp = [[False] * (target + 1) for _ in range(len(nums) + 1)]dp[0][0] = True# 从前 i 个数开始for i in range(1, len(nums) + 1):for j in range(target + 1):if nums[i - 1] <= j:dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]]else:# 如果不选dp[i][j] = dp[i - 1][j]return dp[len(nums)][target]

优化

第一个维度删掉,第二个反过来

class Solution:def canPartition(self, nums: List[int]) -> bool:# 求和total_sum = sum(nums)if total_sum % 2 == 1:return Falsetarget = total_sum // 2# 能否凑出半个 0-1 背包# 考虑前 i 个数字能否凑出 j# f(i, j) = f(i - 1, j) or f(i - 1, j - nums[i])# base case: f(0, j): f(0, 0) = True# 优化# 删除第一个维度,第二层反过来dp = [False] * (target + 1)dp[0] = True# 从前 i 个数开始for num in nums:for j in range(target, num - 1, -1):dp[j] = dp[j] or dp[j - num]return dp[target]
class Solution:def canPartition(self, nums: List[int]) -> bool:# 求和total_sum = sum(nums)if total_sum % 2 == 1:return Falsetarget = total_sum // 2# 能否凑出半个 0-1 背包# 考虑前 i 个数字能否凑出 j# f(i, j) = f(i - 1, j) or f(i - 1, j - nums[i])# base case: f(0, j): f(0, 0) = Truedp = [False] * (target + 1)dp[0] = True# 从前 i 个数开始for i in range(1, len(nums) + 1):for j in range(target, nums[i - 1] - 1, -1):dp[j] = dp[j] or dp[j - nums[i - 1]]return dp[target]

494 目标和

pos - (sum - pos) = target

class Solution:def findTargetSumWays(self, nums: List[int], target: int) -> int:total_sum = sum(nums)if (total_sum + target) % 2 == 1 or total_sum + target < 0:return 0pos = (total_sum + target) // 2"""f(i, j) 考虑前 i 个数字,凑出 j 有多少种方案f(i, j) = f(i-1, j) + f(i-1, j-nums[i])f(0, 0) = 1, f(0, j) = 1"""n = len(nums)# dp = [[0] * (pos + 1) for _ in range(n + 1)]# # 没有数字# dp[0][0] = 1# for i in range(1, n + 1):#     for j in range(pos + 1):#         if nums[i - 1] <= j:#             dp[i][j] = dp[i - 1][j] + dp[i - 1][j - nums[i - 1]]#         else:#             dp[i][j] = dp[i - 1][j]# return dp[n][pos]dp = [0] * (pos + 1) # 没有数字dp[0] = 1for i in range(1, n + 1):for j in range(pos, nums[i - 1] - 1, -1):dp[j] = dp[j] + dp[j - nums[i - 1]]return dp[pos]

474 一和零 0-1 三维-->二维

class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:length = len(strs)# 开两个背包的容量dp = [[0] * (n + 1) for _ in range(m + 1)]"""前 i 个字符串最多 j 个 0 和 k 个 1,可以选的最多字符串数量f(i,j,k) = max(f(i - 1, j, k), f(i - 1, j - zero, k - one) + 1)f(0, j, k) = 00-1 逆序"""for i in range(1, length + 1):# 统计字符串的数量zero, one = 0, 0for c in strs[i - 1]:if c == '0':zero += 1else:one += 1# 逆序for j in range(m, zero - 1, -1):for k in range(n, one - 1, -1):dp[j][k] = max(dp[j][k], dp[j - zero][k - one] + 1)return dp[m][n]
class Solution:def findMaxForm(self, strs: List[str], m: int, n: int) -> int:length = len(strs)# 开两个背包的容量dp = [[0] * (n + 1) for _ in range(m + 1)]"""前 i 个字符串最多 j 个 0 和 k 个 1,可以选的最多字符串数量f(i,j,k) = max(f(i - 1, j, k), f(i - 1, j - zero, k - one) + 1)f(0, j, k) = 00-1 逆序"""for i in range(1, length + 1):zero, one = 0, 0for c in strs[i - 1]:if c == '0':zero += 1else:one += 1for j in range(m, zero - 1, -1):for i in range(n, one - 1, -1):dp[j][i] = max(dp[j][i], dp[j - zero][i - one] + 1)return dp[m][n]

322 零钱兑换 完全背包

class Solution:def coinChange(self, coins: List[int], amount: int) -> int:"""前 i 个硬币凑出 amount 的最小数量f(i, j) = min(f(i - 1, j), f(i - 1, j - coins[i - 1]) + 1)f(0, j) f(0, 0) = 0 else = inf"""n = len(coins)dp = [[0] * (amount + 1) for _ in range(n + 1)]dp[0][0] = 0for j in range(1, amount + 1):dp[0][j] = 10010for i in range(1, n + 1):for j in range(0, amount + 1):if coins[i - 1] <= j:dp[i][j] = min(dp[i - 1][j], dp[i][j - coins[i - 1]] + 1)else:dp[i][j] = dp[i - 1][j]return -1 if dp[n][amount] > amount else dp[n][amount]

正序

class Solution:def coinChange(self, coins: List[int], amount: int) -> int:"""前 i 个硬币凑出 amount 的最小数量f(i, j) = min(f(i - 1, j), f(i - 1, j - coins[i - 1]) + 1)f(0, j) f(0, 0) = 0 else = inf"""n = len(coins)dp = [0] * (amount + 1)dp[0] = 0for j in range(1, amount + 1):dp[j] = 10010for i in range(1, n + 1):for j in range(0, amount + 1):if coins[i - 1] <= j:dp[j] = min(dp[j], dp[j - coins[i - 1]] + 1)return -1 if dp[amount] > amount else dp[amount]

518 零钱兑换

class Solution:def change(self, amount: int, coins: List[int]) -> int:# f(i,j) 考虑前 i 个硬币,凑出 j 有多少种方案?# f(i-1, j) + f(i, j-coins[i-1]) 完全背包n = len(coins)dp = [0] * (amount + 1)dp[0] = 1for i in range(1, n + 1):for j in range(coins[i - 1], amount + 1):dp[j] += dp[j - coins[i - 1]]return dp[amount]

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

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

相关文章

C# 图标标注小工具-查看重复文件

目录 效果 项目 代码 下载 效果 项目 代码 using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; using System.Security.Cryptography; using System.Windows.Forms;namespace ImageDuplicate {public partial clas…

使用Go处理HTTP GET请求

你是否曾经想过&#xff0c;当你点击一个链接或在浏览器中输入一个网址时&#xff0c;背后发生了什么&#xff1f;其实&#xff0c;这是一个小小的数据冒险之旅。而今天&#xff0c;我们将使用Go语言作为我们的冒险伙伴&#xff0c;一起去探索如何处理HTTP GET请求的神秘世界&a…

2023年度总结—你是你的年度MVP吗?

这段年度总结其实我之前就想写了&#xff0c;大概就是市赛比完之后18号的样子把&#xff0c;但是因为太懒了就一直拖到了现在哈哈&#xff0c;我思来想去&#xff0c;翻来覆去&#xff0c;彻夜难眠&#xff0c;想了想&#xff0c;还是决定把它写了吧&#xff01;毕竟&#xff0…

「微服务」微服务架构中的数据一致性

在微服务中&#xff0c;一个逻辑上原子操作可以经常跨越多个微服务。即使是单片系统也可能使用多个数据库或消息传递解决方案。使用多个独立的数据存储解决方案&#xff0c;如果其中一个分布式流程参与者出现故障&#xff0c;我们就会面临数据不一致的风险 - 例如在未下订单的情…

2024 GMF|The Sandbox 为创作者赋能的新时代

以新的 GMF 模型和专门的参与池奖励来开启 2024 年吧。 11 月 3 日&#xff0c;我们在香港全球创作者日上宣布&#xff0c;The Sandbox 已为所有创作者分配了100,000,000 SAND&#xff0c;将通过 GMF 进行分发。作为首次启动的建设者挑战&#xff0c;我们准备了专门的 SAND 参与…

2023 全球 AI 大事件盘点

本文来自微信公众号硅星人

美团到店终端从标准化到数字化的演进之路

总第580篇 | 2023年第032篇 本文整理自美团技术沙龙第76期《大前端研发协同效能提升与实践》。前端团队在产研多角色协同形式上存在不同阶段&#xff0c;而大前端多技术栈在各阶段都有其独特的实践&#xff0c;同时又有类似的演进路线。本文从到店终端团队移动端和前端技术栈持…

vue3引入百度地图(两种方法)

首先要有百度开放平台并进行注册&#xff0c;不懂看这里 ### 第一种方法 地图引入流程 安装vue-baidu-map-3x插件 参考官网地址&#xff1a;快速上手 | vue-baidu-map-3x npm install vue-baidu-map-3x --save 在public/index.html文件中引入 <!-- 百度地图 --> &…

DHCP学习记录

目录 客户端向DHCP服务端申请租用IP的4个阶段: 客户端向HDCP服务器续租IP过程: 客户端重新连接租用IP过程: 客户端释放IP 声明: (Dynamic Host Configuration Protocol)动态主机配置协议&#xff0c;客户端向DHCP服务端申请获得ip的一种约定俗成的话语(协议) 手工配置方式…

Spring Boot笔记1

1. SpringBoot简介 1.1. 原有Spring优缺点分析 1.1.1. Spring的优点分析 Spring是Java企业版&#xff08;Java Enterprise Edition&#xff0c;javeEE&#xff09;的轻量级代替品。无需开发重量级的Enterprise JavaBean&#xff08;EJB&#xff09;&#xff0c;Spring为企业…

macos下php 5.6 7.0 7.4 8.0 8.3 8.4全版本PHP开发环境安装方法

在macos中如果使用brew 官方默认的core tap 只可以安装官方最新的稳定版PHP, 如果想要安装 php 5.6 或者 php 8.4版本的PHP就需要使用第三方的tap , 这里分享一个比较全面的brew tap shivammathur/php 这个tap里面包含了从php5.6到最新版php8.4的所有可用最新版本PHP, 而且是同…