【灵神高频面试题合集17-20】动态规划(上)

发布时间:2026/10/4 17:16:51

【灵神高频面试题合集17-20】动态规划(上) 基础算法精讲·题目汇总灵茶山艾府 - 【基础算法精讲】- GitHub视频灵茶山艾府的个人空间-灵茶山艾府个人主页-哔哩哔哩视频力扣最全 DP 题单分享丨【算法题单】动态规划入门/背包/划分/状态机/区间/状压/数位/树形/优化 - 讨论 - 力扣LeetCode17 从记忆化搜索到递推推荐学习路线二叉树递归 - 回溯 - 记忆化搜索 - 递推动态规划的核心是状态定义和状态转移方程子集型回溯选或不选 / 选哪个两种思路课程讲解198. 打家劫舍选的情况下相邻的房子是不能选的直接递归到 n-2 个房子在定义 dfs 或者 dp 数组的含义时只能表示从一些元素中算出的结果而不是从一个元素中算出的结果没有把得到的金额和作为递归的入参而是作为返回值后面记忆化要用class Solution: def rob(self, nums: List[int]) - int: n len(nums) def dfs(i): if i 0: # 没有房子可以选了 return 0 res max(dfs(i-1), dfs(i-2) nums[i]) return res return dfs(n-1)指数级时间复杂度回溯会超时记忆化搜索优化后的搜索树优化后搜索树只有 O(n) 个节点因此时间复杂度也优化到了 O(n)对于Python可以用一个 cache 装饰器原理是用一个 hashmap 记录入参和对应的返回值在 Python 中cache是 Python 3.9 版本引入的一个装饰器用于自动缓存函数的计算结果记忆化搜索非常适合深度优先搜索DFS场景可以避免重复计算大大提升运行速度具体的引入方式from functools import cacheclass Solution: def rob(self, nums: List[int]) - int: n len(nums) cache [-1] * n def dfs(i): if i 0: return 0 if cache[i] ! -1: return cache[i] res max(dfs(i-1), dfs(i-2) nums[i]) cache[i] res return res return dfs(n-1)时间复杂度状态个数 × 单个状态所需要的计算时间。前者 O(n)后者 O(1)所以时间复杂度为 O(n)空间复杂度O(n)class Solution: def rob(self, nums: List[int]) - int: n len(nums) f [0] * (n2) for i, x in enumerate(nums): f[i2] max(f[i1], f[i] x) return f[n1]【答疑】为什么 nums【i】 中的 i 不需要 2。 第一这会导致 nums【0】 和 nums【1】 无法算进答案中。 第二当 in-1 时i2n1这会导致 nums 数组越界。 另外一种理解方式是我们只是在 f 数组的开头插入了两个状态对应记忆化搜索中的 dfs(-2) 和 dfs(-1)这只会影响到 f 的下标不会影响到 nums 的下标。上面代码的空间复杂度仍然是 O(n) 的。优化空间复杂度为 O(1)class Solution: def rob(self, nums: List[int]) - int: n len(nums) f0 f1 0 for i, x in enumerate(nums): new_f max(f1, f0 x) f0 f1 f1 new_f return f1 # 最后一次算出来的 new_f课后作业70. 爬楼梯746. 使用最小花费爬楼梯3693. 爬楼梯 II213. 打家劫舍 II740. 删除并获得点数2466. 统计构造好字符串的方案数377. 组合总和 Ⅳ2266. 统计打字方案数64. 最小路径和18 0-1背包 完全背包课程讲解0-1背包# capacity背包容量 # w[i]第 i 个物品的体积 # v[i]第 i 个物品的价值 # 返回所选物品体积和不超过 capacity 的前提下所能得到的最大价值和 def zero_one_knapsack(capacity: int, w: List[int], v: List[int]) - int: n len(w) cache def dfs(i, c): if i 0: return 0 if c w[i]: # 物品体积已经超过背包剩余容量只能不选 return dfs(i-1, c) return max(dfs(i-1, c), dfs(i-1, c-w[i]) v[i]) return dfs(n-1, capacity)cache 这一行的作用是改成记忆化搜索494. 目标和class Solution: def findTargetSumWays(self, nums: list[int], target: int) - int: # 添加正数的和记为p # 添加负数的和 所有元素的和 - p s-p # target p - (s-p) 推导出 p (s target) / 2 # 问题变成从nums中选择一些数字使它们的和恰好等于 (s target) / 2 的方案数 # s target 必须是偶数 非负数 # dfs(i, c) 表示从前 i 个数中选一些数恰好组成 c 的方案数 target sum(nums) if target 0 or target % 2: # 负数或奇数方案数就是0 return 0 target // 2 n len(nums) cache def dfs(i, c): if i 0: return 1 if c 0 else 0 # c是target减到0就找到了一组方案 if c nums[i]: return dfs(i-1, c) return dfs(i-1, c) dfs(i-1, c-nums[i]) return dfs(n-1, target)时间复杂度O (n * target) 状态个数 * 每个状态所需的时间 O(1)空间复杂度O (n * target)优化空间复杂度把记忆化搜索改成递推class Solution: def findTargetSumWays(self, nums: list[int], target: int) - int: # 添加正数的和记为p # 添加负数的和 所有元素的和 - p s-p # target p - (s-p) 推导出 p (s target) / 2 # 问题变成从nums中选择一些数字使它们的和恰好等于 (s target) / 2 的方案数 # s target 必须是偶数 非负数 # dfs(i, c) 表示从前 i 个数中选一些数恰好组成 c 的方案数 target sum(nums) if target 0 or target % 2: return 0 target // 2 n len(nums) f [[0] * (target1) for _ in range(n1)] f[0][0] 1 for i, x in enumerate(nums): for c in range(target1): if c x: f[i1][c] f[i][c] else: f[i1][c] f[i][c] f[i][c-x] return f[n][target]每时每刻只有两个数组中的元素在参与状态转移只需要用到两个数组把所有的和 i 相关的都改成 模2这样就把空间复杂度优化到 O(target)n len(nums) f [[0] * (target1) for _ in range(2)] f[0][0] 1 for i, x in enumerate(nums): for c in range(target1): if c x: f[(i1)%2][c] f[i%2][c] else: f[(i1)%2][c] f[i%2][c] f[i%2][c-x] return f[n%2][target]优化成一个一维数组倒着算就不会被覆盖n len(nums) f [0] * (target1) f[0] 1 for x in nums: for c in range(target, x-1, -1): f[c] f[c] f[c-x] return f[target]如果是至多为targetdef findTargetSumWays(nums, target): target sum(nums) if target 0: return 0 target // 2 # 问题变成从 nums 中选出一个子集使子集和 target 的方案数 f [1] * (target1) # 初始化为1至多为target时不选择元素就可以作为一种合理方案 for x in nums: for c in range(target, x-1, -1): f[c] f[c] f[c-x] return f[target] # 或记忆化搜索的写法 from functools import cache def findTargetSumWays(nums, target): target sum(nums) if target 0: return 0 target // 2 n len(nums) cache def dfs(i, c): # 从前 i 个元素中选子集使子集和 c 的方案数 if i 0: return 1 if c nums[i]: return dfs(i-1, c) return dfs(i-1, c) dfs(i-1, c-nums[i]) return dfs(n-1, target)如果是至少为targetdef findTargetSumWays(nums, target): target sum(nums) if target 0: return 1 len(nums) target (target 1) // 2 f [0] * (target1) f[0] 1 # 剩余需要凑的和 0 时空集满足方案数为 1 for x in nums: for c in range(target, -1, -1): f[c] f[c] f[max(c-x, 0)] # 把所有 c0 的状态都记录到 f[0] 里 return f[target] # 或 from functools import cache def findTargetSumWays(nums, target): target sum(nums) if target 0: return 1 len(nums) target (target 1) // 2 n len(nums) cache def dfs(i, c): if i 0: return 1 if c 0 else 0 return dfs(i-1, c) dfs(i-1, c-nums[i]) return dfs(n-1, target)完全背包和 01背包的回溯 区别在选了一个物品之后i是不变的表示可以继续选第i种物品# capacity背包容量 # w[i]第 i 种物品的体积 # v[i]第 i 种物品的价值 # 每种物品可以无限次重复选 # 返回所选物品体积和不超过 capacity 的前提下所能得到的最大价值和 def unbounded_knapsack(capacity: int, w: List[int], v: List[int]) - int: n len(w) cache def dfs(i, c): if i 0: return 0 if c w[i]: return dfs(i-1, c) return max(dfs(i-1, c), dfs(i, c-w[i]) v[i]) # 唯一区别 return dfs(n-1, capacity)322. 零钱兑换完全背包的一种变形把物品价值看成1class Solution: def coinChange(self, coins: list[int], amount: int) - int: n len(coins) cache def dfs(i, c): if i 0: return 0 if c 0 else inf # inf表示不是一种合法的方案 if c coins[i]: return dfs(i-1, c) return min(dfs(i-1, c), dfs(i, c-coins[i]) 1) ans dfs(n-1, amount) return ans if ans inf else -1改成递推class Solution: def coinChange(self, coins: list[int], amount: int) - int: n len(coins) f [[inf] * (amount1) for _ in range(n1)] f[0][0] 0 for i, x in enumerate(coins): for c in range(amount1): # c表示剩余容量 if c x: f[i1][c] f[i][c] else: f[i1][c] min(f[i][c], f[i1][c-x] 1) ans f[n][amount] return ans if ans inf else -1空间优化一维数组对于完全背包正序计算是对的。空间复杂度优化到了 O(amount)class Solution: def coinChange(self, coins: list[int], amount: int) - int: n len(coins) f [inf] * (amount1) f[0] 0 for x in coins: for c in range(x, amount1): f[c] min(f[c], f[c-x] 1) ans f[amount] return ans if ans inf else -1循环顺序总结一维数组01背包倒序完全背包正序二维数组 一般都写正序变形如求方案数的话有至多恰好至少课后作业2915. 和为目标值的最长子序列的长度416. 分割等和子集2787. 将一个数字表示成幂的和的方案数518. 零钱兑换 II279. 完全平方数19 线性dp上在默认情况下子数组和子串是连续的子序列不一定是连续的课程讲解1143. 最长公共子序列 LCS在 s[i] t[j] 时只需要考虑都选的情况在 s[i] ≠ t[j] 时不需要考虑都不选的情况class Solution: def longestCommonSubsequence(self, text1: str, text2: str) - int: n len(text1) m len(text2) cache def dfs(i, j): if i 0 or j 0: return 0 if text1[i] text2[j]: return dfs(i-1, j-1) 1 return max(dfs(i-1, j), dfs(i, j-1)) return dfs(n-1, m-1)时间复杂度O(nm)空间同改成递推class Solution: def longestCommonSubsequence(self, text1: str, text2: str) - int: n len(text1) m len(text2) f [[0] * (m1) for _ in range(n1)] for i, x in enumerate(text1): for j, y in enumerate(text2): if x y: f[i1][j1] f[i][j] 1 else: f[i1][j1] max(f[i][j1], f[i1][j]) return f[n][m]优化成一个一维数组空间复杂度为 O(m)72. 编辑距离class Solution: def minDistance(self, word1: str, word2: str) - int: n len(word1) m len(word2) cache def dfs(i, j): if i 0: return j1 # 一个字符串为空需要把另一个字符串都去掉 if j 0: return i1 if word1[i] word2[j]: return dfs(i-1, j-1) else: return min(dfs(i-1, j), dfs(i, j-1), dfs(i-1, j-1)) 1 return dfs(n-1, m-1)改成递推初始化f[0][j] jf[i][0] iclass Solution: def minDistance(self, word1: str, word2: str) - int: n len(word1) m len(word2) f [[0] * (m1) for _ in range(n1)] f[0] list(range(m1)) # f[0][j] j for i, x in enumerate(word1): f[i1][0] i1 # f[i][0] i for j, y in enumerate(word2): if x y: f[i1][j1] f[i][j] else: f[i1][j1] min(f[i1][j], f[i][j1], f[i][j]) 1 return f[n][m]课后作业583. 两个字符串的删除操作712. 两个字符串的最小ASCII删除和97. 交错字符串1458. 两个子序列的最大点积1092. 最短公共超序列20 线性dp下课程讲解300. 最长递增子序列 LIS所谓子序列就是从数组中选择一些数且顺序和数组中的顺序是一致的回溯/动态规划对于子集型回溯用思路2更简单记忆化搜索class Solution: def lengthOfLIS(self, nums: list[int]) - int: n len(nums) cache def dfs(i): # 表示以 nums[i] 结尾的子序列长度 res 0 for j in range(i): # 枚举 i 前面的 j if nums[j] nums[i]: res max(res, dfs(j)) return res 1 # 这里的 1 表示 nums[i] ans 0 for i in range(n): ans max(ans, dfs(i)) return ans时间复杂度O(n^2)。O(n) 个状态计算每个状态需要 O(n) 的时间空间复杂度O(n)递推时空间复杂度同上class Solution: def lengthOfLIS(self, nums: list[int]) - int: n len(nums) f [0] * n for i in range(n): for j in range(i): if nums[j] nums[i]: f[i] max(f[i], f[j]) f[i] 1 return max(f)最长递增子序列 vs 最长公共子序列 是有联系的贪心二分这种方法时间复杂度优化到 O(nlogn)class Solution: def lengthOfLIS(self, nums: list[int]) - int: g [] for x in nums: j bisect_left(g, x) # 二分查找 x 在 g 中的位置 if j len(g): # j 不存在 g.append(x) else: g[j] x return len(g)空间复杂度 O(n)可以直接把 nums 当做 g 数组这样空间复杂度就优化到 O(1) 了class Solution: def lengthOfLIS(self, nums: list[int]) - int: ng 0 # 表示g的长度 for x in nums: j bisect_left(nums, x, 0, ng) # 直接在nums上二分范围是0~ng if j ng: # 表示j不存在 nums[ng] x ng 1 # g数组长度1 else: nums[j] x return ng对应到代码中就是把 bisect_left 改成 bisect_right课后作业2826. 将三个组排序1964. 找出到每个位置为止最长的有效障碍赛跑路线1671. 得到山形数组的最少删除次数2111. 使数组 K 递增的最少操作次数354. 俄罗斯套娃信封问题1626. 无矛盾的最佳球队1187. 使数组严格递增
延伸阅读

更多相关文章

2026/10/4 17:11:51

SSM+Vue汽车售票网站:从业务设计到并发数据一致性

做毕设那会儿,我周围不少同学都扎堆去做"网上商城""图书管理系统"这类选题,结果答辩时老师问两句并发控制就卡住了。我当时选了"基于JAVA的汽车售票网站",理由很简单:汽车票务这个场景天然包含车次…

2026/10/4 17:11:50

VS Code Codex 本地代理接入 DeepSeek 模型实战指南

1. 先说清楚:Codex 和 DeepSeek 到底是什么关系,别被标题带偏了很多人看到“Codex 接入 DeepSeek”这个标题,第一反应是:“Codex 是 GitHub 官方推出的 AI 编程助手,DeepSeek 是国产大模型,难道 GitHub 官方…

2026/10/4 21:42:02

omofun动漫|安卓安装|官网入口和追番入门

第一次接触 OmoFun动漫,可以先把它看作一处面向动画爱好者的内容入口:打开后,不必急着寻找某一部作品,不妨先浏览首页推荐、分类栏目与专题信息,了解平台的页面布局,再按自己的兴趣逐步筛选。不同版本的界面…

2026/10/4 21:42:02

Ace Data Cloud 接入 GLM 实战:Chat Completion API 与流式输出全攻略

最近我一直在折腾怎么把大模型对话能力接到现有产品里,问得最多的问题就是“你的 GLM 接口怎么接的”“用了什么平台”。这篇我直接把我完整的接入过程交底:从 Ace Data Cloud 上开通 GLM 模型、拿到 Chat Completion API 的调用凭证,到 Pyth…

2026/10/4 21:42:02

芯片烧录本质:ISP、ICP、IAP三者原理与工程实践辨析

1. 芯片烧录不是“刷机”,而是给芯片装上第一行能跑起来的代码 很多人第一次接触单片机开发,看到“烧录”这个词,下意识联想到手机刷机、U盘拷文件——这其实是个危险的误解。我带过不少刚毕业的实习生,他们第一次用ST-Link往STM3…

2026/10/4 21:42:02

从零训练中文语言模型:手写Transformer与预训练微调全流程

把“AI engineering from scratch”当口号的人很多,真正从零手搓过一遍的人比例很低。我去年完整走过一遍:自己清洗数据、从零训练分词器、手写Transformer核心模块、把小模型喂到收敛、再做推理能力微调。整个过程如果用商业眼光衡量确实不划算&#xf…

2026/10/4 21:42:02

MIPI LP RX硬件设计实战:从信号完整性到FPGA实现

1. 项目概述:MIPI LP RX到底在解决什么问题?MIPI LP RX——这个缩写组合乍看像一串技术代号,实则直指一个高频、高痛、高门槛的硬件接口工程现场:低功耗(Low-Power)模式下的MIPI接收端(Receiver…

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/4 0:01:02

Jev+Agent接管浏览器:browser-use实战与jev-ultrafast性能优化

1. 从“Jev”说起:为什么我要把Agent接进浏览器“Jev”这个词最近在圈子里出现的频率越来越高,很多人第一次听到会以为是某个新模型的名字,其实它更像是一种思路——把Jev模型的能力当作底座,通过Agent的方式去接管浏览器&#xf…

2026/10/4 0:01:02

多智能体集群实战:DeepAgents编排、MCP与A2A协议及Skills体系

1. 从"单兵作战"到"集群协同":多智能体编排到底在解决什么问题如果你最近在折腾 Agent 相关的东西,大概率会有一种感觉:单个 Agent 能做的事情,其实很快就摸到天花板了。你给它一个提示词,挂几个工…

2026/10/4 1:01:05

无源低通滤波器设计实战:从RC到LC,手把手教你避开那些坑

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

还想了解更多?直接咨询顾问

免费诊断 + 免费方案 + 透明报价。

全国咨询热线400-8866-253
免费获取方案
☎咨询二维码 ☎ ↑