动态规划适用于子结构具备如下性质的问题:
- 重复子问题
- 最优子结构:问题的最优解所包含的子问题的解也是最优的(子问题的最优解一定包含在原问题的最优解之中)
解题的时候,画问题调用图,有助于获取递推式
-
例子:编辑距离 https://baike.baidu.com/item/%E7%BC%96%E8%BE%91%E8%B7%9D%E7%A6%BB/8010193?fr=aladdin
dp_table(i,j) --> dp_table(i,j+1) | \ | | \ | v \ v dp_table(i+1,j) --> dp_table(i+1, j+1) [被重复计算]def minDistance(self, word1, word2): """ :type word1: str :type word2: str :rtype: int """ #f(word1[i:n], word2[j:m]) = min{f(word1[i:n], word2[j+1:m]) + 1, # f(word1[i+1:n], word2[j:m]) + 1, # f(word1[i+1:n], word2[j+1:m]) + (word1[i] != word2[j])} if word1 == word2: return 0 if word1 == '': return len(word2) if word2 == '': return len(word1) # ! bad version ! # return min(1 + self.minDistance(word1, word2[1:]), # 1 + self.minDistance(word1[1:], word2), # (1 if word1[0] != word2[0] else 0) + self.minDistance(word1[1:], word2[1:])) # ! correct version ! len1 = len(word1) len2 = len(word2) dp_table = [[0 for _ in range(len1+1)] for _ in range(len2+1)] for i in range(len2): dp_table[i][-1] = len2 - i for j in range(len1): dp_table[-1][j] = len1 - j for i in range(len2-1, -1, -1): for j in range(len1-1, -1, -1): delta = 0 if word1[j] == word2[i] else 1 dp_table[i][j] = min(1 + dp_table[i+1][j], 1 + dp_table[i][j+1], delta + dp_table[i+1][j+1]) return dp_table[0][0] -
题型1:组合优化,被组合的东西必须连续
-
前缀和 https://juejin.cn/post/6944913393627168798
https://leetcode.cn/problems/maximum-difference-between-increasing-elements/
-
和为K的子数组: https://leetcode-cn.com/problems/QTMn0o/
-
区域和检索: https://leetcode-cn.com/problems/range-sum-query-2d-immutable/, https://leetcode-cn.com/problems/range-sum-query-immutable/
-
连续子数组的最大和 https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/
题目描述:输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。 要求时间复杂度为O(n)。 示例1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4] 输出: 6 解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。-
解答1:用f(i)f(i)f(i) 代表以第 iii 个数【结尾】的连续子数组的最大和,要求的答案就是:
max0≤i≤n−1{f(i)}\max_{0 \leq i \leq n-1} \{ f(i) \}0≤i≤n−1max{f(i)}
对f(i)f(i)f(i),考虑是否将nums[i]nums[i]nums[i]并入f(i−1)f(i-1)f(i−1)对应的那一段数组,得到递推式:
f(i)=max{nums[i],f(i−1)+nums[i]}f(i)=\max\{nums[i], f(i-1) + nums[i]\}f(i)=max{nums[i],f(i−1)+nums[i]}
-
解答2:用f(i)f(i)f(i) 代表以第 iii 个数【开头】的连续子数组的最大和,要求的答案就是:
max0≤i≤n−1{f(i)}\max_{0 \leq i \leq n-1} \{ f(i) \}0≤i≤n−1max{f(i)}
对f(i)f(i)f(i),考虑是否将nums[i]nums[i]nums[i]并入f(i+1)f(i+1)f(i+1)对应的那一段数组,得到递推式:
f(i)=max{nums[i],f(i+1)+nums[i]}f(i)=\max\{nums[i], f(i+1) + nums[i]\}f(i)=max{nums[i],f(i+1)+nums[i]}
-
-
连续子数组的最大积 https://leetcode.cn/problems/maximum-product-subarray/
-
-
题型2:等式约束组合优化
-
零钱兑换 https://leetcode.cn/problems/coin-change/
题目描述:给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。 你可以认为每种硬币的数量是无限的。 示例1: 输入:coins = [1, 2, 5], amount = 11 输出:3 解释:11 = 5 + 5 + 1 示例2: 输入:coins = [2], amount = 3 输出:-1 示例3: 输入:coins = [1], amount = 0 输出:0-
解答:该问题可建模为以下优化问题:
min∑i=1nxi subject to∑i=1nxi∗ci=S\min \sum_{i=1}^{n} x_i\ \ \ \ \text{subject to} \sum_{i=1}^{n} x_i * c_i = Smini=1∑nxi subject toi=1∑nxi∗ci=S
其中,SSS 是总金额,cic_ici是第iii 枚硬币的面值,xix_ixi是iii 枚硬币的数量。
使用f(i)f(i)f(i)表示组成金额iii所需最少的硬币数量,问题所求的是f(amount)f(amount)f(amount)。假设f(i)f(i)f(i)对应的方案组合已知,从中拿掉一个面值为ccc的硬币,则剩下的硬币为f(i−c)f(i-c)f(i−c)对应的最优方案(否则的话,f(i)f(i)f(i)对应的不是最优方案,产生矛盾),因此f(i)f(i)f(i)的递推式为:
f(i)=minj=1nf(i−cj)+1, 1≤i≤amountf(i) = \min_{j=1}^n f(i-c_j) + 1,\ \ \ \ 1\leq i \leq amountf(i)=j=1minnf(i−cj)+1, 1≤i≤amount
注意到i−cji-c_ji−cj可能为0或者为负,因此要补充定义f(0)=0f(0)=0f(0)=0,如果i=cji=c_ji=cj,那么一个硬币就可以实现目标,原式子依然成立。完整的递推式为:
f(i)=0, i=0f(i) = 0, \ \ \ \ i=0f(i)=0, i=0
f(i)=minj=1,cj≤inf(i−cj)+1, 1≤i≤amountf(i) = \min_{j=1,c_j\leq i}^n f(i-c_j) + 1,\ \ \ \ 1\leq i \leq amountf(i)=j=1,cj≤iminnf(i−cj)+1, 1≤i≤amount
-
-
单词拆分 https://leetcode.cn/problems/word-break/
-
-
题型3:不等式约束组合优化,被组合的东西之间是并列关系,即背包问题及其变种
-
01背包问题
题目描述:有 N 件物品和一个容量是 V 的背包。每件物品只能使用一次。 第 i 件物品的体积是 v[i],价值是 w[i]。 求解将哪些物品装入背包,可使这些物品的总体积不超过背包容量,且总价值最大。输出最大价值。- 解答:01背包问题是一个不等式约束的组合优化问题,即
max∑i=1nwixi subject to∑i=1nvi∗xi≤V, xi=0,1\max \sum_{i=1}^{n} w_i x_i\ \ \ \ \text{subject to} \sum_{i=1}^{n} v_i * x_i \leq V,\ \ x_i=0,1maxi=1∑nwixi subject toi=1∑nvi∗xi≤V, xi=0,1
使用f(i,j)f(i,j)f(i,j)表示:只从前iii个物品中选,并且总体积不超过jjj的选法,所能达到的最大价值。则问题所求的是f(N,V)f(N,V)f(N,V)。
对于第iii个物品,存在选与不选两个操作,如果选择,则前i−1i-1i−1个物品的总体积不能超过j−v[i]j-v[i]j−v[i],因此,递推式为:
f(i,j)=max(f(i−1,j),f(i−1,j−v[i])+w[i])f(i,j) = \max(f(i-1,j), f(i-1, j-v[i]) + w[i])f(i,j)=max(f(i−1,j),f(i−1,j−v[i])+w[i])
边界情况:i−1i-1i−1可能为0,此时即没有任何物品,总价值自然就是0,所以安排f(0,:)f(0,:)f(0,:)这一行都是0即可。此外,j−v[i]j-v[i]j−v[i]也有可能为0或者为负,当为0时候,表示体积限制为0,总价值自然也是0,所以安排f(:,0)f(:,0)f(:,0)这一列也都是0,此外,限制j<v[i]j<v[i]j<v[i] 时候,f(i,j)=f(i−1,j)f(i,j)=f(i-1,j)f(i,j)=f(i−1,j)。完整的递推式为:
f(i,j)=0, i=0 or j=0f(i,j) = 0,\ \ \ \ i=0\ \ \text{or}\ \ j=0f(i,j)=0, i=0 or j=0
f(i,j)=f(i−1,j) 1≤i≤N,1≤j<v[i]f(i,j) = f(i-1,j)\ \ \ \ 1\leq i\leq N, 1\leq j < v[i]f(i,j)=f(i−1,j) 1≤i≤N,1≤j<v[i]
f(i,j)=max(f(i−1,j),f(i−1,j−v[i])+w[i]), 1≤i≤N,v[i]≤j≤Vf(i,j) = \max(f(i-1,j), f(i-1, j-v[i]) + w[i]),\ \ \ \ 1\leq i\leq N, v[i]\leq j\leq Vf(i,j)=max(f(i−1,j),f(i−1,j−v[i])+w[i]), 1≤i≤N,v[i]≤j≤V -
完全背包问题
题目描述:同上,但是不限制每件物品的使用次数。- 解答:递推式为:
f(i,j)=0, i=0 or j=0f(i,j) = 0,\ \ \ \ i=0\ \ \text{or}\ \ j=0f(i,j)=0, i=0 or j=0
f(i,j)=maxk=0k∗v[i]≤j(f(i−1,j−k∗v[i])+k∗w[i]), 1≤i≤N,1≤j≤Vf(i,j) = \max_{k=0}^{k*v[i] \leq j}(f(i-1, j-k*v[i]) + k*w[i]),\ \ \ \ 1\leq i\leq N, 1\leq j\leq Vf(i,j)=k=0maxk∗v[i]≤j(f(i−1,j−k∗v[i])+k∗w[i]), 1≤i≤N,1≤j≤V -
选举
题目描述:有 N 个州,第 i 个州的人口为 p[i],投票数为 v[i]。给定最小需要的票数为V 求解最少需要的人口。- 解答:原问题为:
min∑i=1npixi subject to∑i=1nvi∗xi≥V, xi=0,1\min \sum_{i=1}^{n} p_i x_i\ \ \ \ \text{subject to} \sum_{i=1}^{n} v_i * x_i \geq V,\ \ x_i=0,1mini=1∑npixi subject toi=1∑nvi∗xi≥V, xi=0,1
转化为:
max∑i=1n−pixi subject to∑i=1n−vi∗xi≤−V, xi=0,1\max \sum_{i=1}^{n} -p_i x_i\ \ \ \ \text{subject to} \sum_{i=1}^{n} -v_i * x_i \leq -V,\ \ x_i=0,1maxi=1∑n−pixi subject toi=1∑n−vi∗xi≤−V, xi=0,1
为了消去负值,使用全体求和,即
max∑i=1n−pixi<−>∑i=1npi+max∑i=1n−pixi=∑i=1npi(1−xi)\max \sum_{i=1}^{n} -p_i x_i <-> \sum_{i=1}^{n} p_i +\max \sum_{i=1}^{n} -p_i x_i = \sum_{i=1}^{n} p_i (1 - x_i)maxi=1∑n−pixi<−>i=1∑npi+maxi=1∑n−pixi=i=1∑npi(1−xi)
∑i=1n−vi∗xi≤−V<−>∑i=1nvi+∑i=1n−vi∗xi≤∑i=1nvi−V<−>∑i=1nvi(1−xi)≤∑i=1nvi−V\sum_{i=1}^{n} -v_i * x_i \leq -V <-> \sum_{i=1}^{n} v_i + \sum_{i=1}^{n} -v_i * x_i \leq \sum_{i=1}^{n} v_i - V <-> \sum_{i=1}^{n} v_i (1- x_i) \leq \sum_{i=1}^{n} v_i - Vi=1∑n−vi∗xi≤−V<−>i=1∑nvi+i=1∑n−vi∗xi≤i=1∑nvi−V<−>i=1∑nvi(1−xi)≤i=1∑nvi−V
由于xi=0,1x_i=0,1xi=0,1,1−xi1-x_i1−xi也是0或者1,所以问题成为:
max∑i=1npixi subject to∑i=1nvi∗xi≤∑i=1nvi−V, xi=0,1\max \sum_{i=1}^{n} p_i x_i\ \ \ \ \text{subject to} \sum_{i=1}^{n} v_i * x_i \leq \sum_{i=1}^{n} v_i-V,\ \ x_i=0,1maxi=1∑npixi subject toi=1∑nvi∗xi≤i=1∑nvi−V, xi=0,1
这是标准的01背包问题,只不过求解最后把选择的情况反选即可。
边界情况:如果 ∑i=1nvi<V\sum_{i=1}^{n} v_i<V∑i=1nvi<V,问题无解,因为凑了所有选票也达不到要求。
-
-
题型4:不等式约束组合优化,组合的事物之间有马尔科夫性,即下一个选择的候选集取决于上一个选择
-
跳跃游戏:https://leetcode.cn/problems/jump-game/
题目描述:给定一个非负整数数组 nums ,你最初位于数组的 第一个下标 。 数组中的每个元素代表你在该位置可以跳跃的最大长度。 判断你是否能够到达最后一个下标。 示例1: 输入:nums = [2,3,1,1,4] 输出:true 解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。 示例2: 输入:nums = [3,2,1,0,4] 输出:false 解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。-
解答1:用f(i)f(i)f(i) 表示从iii出发能不能到达最后一个下标nnn,明显f(n)=1f(n)=1f(n)=1,而要求的答案为f(0)f(0)f(0)。
从i+1i+1i+1到i+nums[i]i+nums[i]i+nums[i]中,只要有fff值为1,则f(i)f(i)f(i)为1,递推式为:
f(i)=f(i+1) or f(i+2) or ... or f(i+nums[i])f(i) = f(i+1)\ \text{or}\ f(i+2)\ \text{or}\ ...\ \text{or}\ f(i+nums[i])f(i)=f(i+1) or f(i+2) or ... or f(i+nums[i])
但是式子右边可能会出现超过nnn的下标,为了规避这一点,修改为:
f(i)=maxj=i+1min{i+nums[i],n}fjf(i) = \max_{j=i+1}^{\min\{i+nums[i], n\}} f_jf(i)=j=i+1maxmin{i+nums[i],n}fj
复杂度O(n∗max(nums))O(n*\max(nums))O(n∗max(nums))
''' V1 执行用时:2948 ms, 在所有 Python 提交中击败了5.00%的用户 通过测试用例:170 / 170 ''' def canJump(self, nums: List[int]) -> bool: result = [False] * len(nums) result[-1] = True for i in range(len(nums)-2, -1, -1): for j in range(i+1, min(len(nums), i+1+nums[i])): if result[j]: result[i] = True break return result[0] -
解答2:贪心算法
def canJump(self, nums: List[int]) -> bool: right_most = 0 # 记录最远能跳到哪 for idx, val in enumerate(nums[:-1]): # 忽略最后一个位置 right_most = max(right_most, idx+val) # 当前位置上,最远能跳到哪 # 如果最远都无法超过当前位置,那肯定无法到达最后一个位置,提前结束 if right_most <= idx: return False return True
-
-
打家劫舍 https://leetcode.cn/problems/house-robber/, https://leetcode.cn/problems/house-robber-ii/
-
礼物的最大价值 https://leetcode.cn/problems/li-wu-de-zui-da-jie-zhi-lcof/
-
最长“自定义”子序列
题目描述:自定义可以是递增,递减或者相邻元素满足一定条件(如相差不超过5)- 解答:用f(i)f(i)f(i) 表示从数组开头(以1 为第一个下标)到第i个位置的满足条件的最长子序列长度。
f(i)=max1≤j<i,∣nums[j]−nums[i]∣≤5f(j)+1f(i)=\max_{1\leq j < i, |nums[j]-nums[i]|\leq 5} f(j) + 1f(i)=1≤j<i,∣nums[j]−nums[i]∣≤5maxf(j)+1
讨论两个边界情况:1. i=1i=1i=1时候,显然f(i)=1f(i)=1f(i)=1; 2. 当1≤j<i1\leq j <i1≤j<i中没有条件满足∣nums[j]−nums[i]∣≤5|nums[j]-nums[i]|\leq 5∣nums[j]−nums[i]∣≤5时候,显然f(i)=1。综合起来,初始化f(i)=1,1≤i≤Nf(i)=1,1\leq i\leq Nf(i)=1,1≤i≤N即可。
-
-
题型5:穷举所有可能的组合
-
青蛙跳台阶问题 https://leetcode.cn/problems/qing-wa-tiao-tai-jie-wen-ti-lcof/
【这个例子可以很好地解释,动态规划可以从给定的数组左边开始计算,也可以从右边开始计算,具体取决于动态规划变量的定义。做题时候想到了如何分解子问题,就按照分解子问题的方式去做,不必拘泥于下标从哪里开始。】
题目描述:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 示例 1: 输入:n = 2 输出:2 示例 2: 输入:n = 7 输出:21 示例 3: 输入:n = 0 输出:1-
解答1:用f(i)f(i)f(i) 表示【到达】第iii 级台阶的跳法种数,题目所求的结果为f(n)f(n)f(n)。
要到达第iii 级台阶,可从第i−1i-1i−1 级台阶或第i−2i-2i−2 级台阶过来,所以递推式为:
f(i)=f(i−1)+f(i−2)f(i)=f(i-1)+f(i-2)f(i)=f(i−1)+f(i−2)
边界情况:由于i−1,i−2i-1,i-2i−1,i−2可能越界,因为当n<2n<2n<2的时候,直接返回结果1。完整的递推式为:
f(i)=1 i=0,1f(i)=1\ \ i=0,1f(i)=1 i=0,1
f(i)=f(i−1)+f(i−2) n≥i≥2f(i)=f(i-1)+f(i-2) \ \ n\geq i\geq 2f(i)=f(i−1)+f(i−2) n≥i≥2实际写代码的时候,不需要记住整个数组fff,只需要使用三个数字即可,代码如下:
def numWays(self, n: int) -> int: if n <= 1: return 1 a = 1 b = 2 for i in range(2, n): c = a + b a = b b = c return b % 1000000007 -
解答2:用f(i)f(i)f(i) 表示从第iii 级到第nnn 级台阶的跳法种数,题目所求的结果为f(0)f(0)f(0)。
递推式为:
f(i)=f(i+1)+f(i+2)f(i)=f(i+1)+f(i+2)f(i)=f(i+1)+f(i+2)
边界情况:由于i+1,i+2i+1,i+2i+1,i+2可能越界,因为当n≥i>n−2n\geq i>n-2n≥i>n−2的时候,直接返回结果1。完整的递推式为:
f(i)=1 i=n−1,nf(i)=1\ \ i=n-1, nf(i)=1 i=n−1,n
f(i)=f(i−1)+f(i−2) 0≤i≤n−2f(i)=f(i-1)+f(i-2) \ \ 0\leq i \leq n-2f(i)=f(i−1)+f(i−2) 0≤i≤n−2实际写代码的时候,只有微小不同:
def numWays(self, n: int) -> int: if n <= 1: return 1 a = 1 b = 1 for i in range(n-2, -1, -1): c = a + b a = b b = c return b % 1000000007
-
-
不同的二叉搜索树 https://leetcode.cn/problems/unique-binary-search-trees/
题目描述:给你一个整数 n ,求恰由 n 个节点组成且节点值从 1 到 n 互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。 示例 1: 输入:n = 3 输出:5 示例 2: 输入:n = 1 输出:1-
解答:使用f(i)f(i)f(i)表示iii个数字二叉搜索树的种数,问题所求的是f(n)f(n)f(n)
由于二叉搜索树的定义(左子树的最大者小于根,右子树的最小者大于根,并且左右子树要么只有一个节点,要么也是二叉搜索树),确定iii作为根节点之后,左子树只能由i−1i-1i−1个数组成,
【注意,这里不要拘泥于数字从1到i-1,因为从2到i也是一样的,重要的是有多少个数字】
右子树只能由n−in-in−i个数组成,因此递推式为:
f(i)=∑j=1if(j−1)∗f(i−j),1≤i≤nf(i) = \sum_{j=1}^{i} f(j-1) * f(i-j), 1\leq i \leq nf(i)=j=1∑if(j−1)∗f(i−j),1≤i≤n
边界情况:无
-
-
不同路径 https://leetcode.cn/problems/unique-paths/
题目描述:一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。 问总共有多少条不同的路径? 示例 1: 输入:m = 3, n = 7 输出:28 示例 2: 输入:m = 3, n = 2 输出:3 解释: 从左上角开始,总共有 3 条路径可以到达右下角。 1. 向右 -> 向下 -> 向下 2. 向下 -> 向下 -> 向右 3. 向下 -> 向右 -> 向下 示例 3: 输入:m = 7, n = 3 输出:28 示例 4: 输入:m = 3, n = 3 输出:6-
解答:使用f(i,j)f(i,j)f(i,j)表示从(i,j)(i,j)(i,j)到(m,n)(m,n)(m,n)的不同路径数量,递推式为
f(i,j)=f(i+1,j)+f(i,j+1)f(i,j) = f(i+1,j) + f(i,j+1)f(i,j)=f(i+1,j)+f(i,j+1)
边界情况,由于i+1,j+1i+1,j+1i+1,j+1可能越界,因此对f(m,:),f(:,n)f(m,:),f(:,n)f(m,:),f(:,n)都设为1即可。完整递推式为:
f(i,j)=1, i=m or j=nf(i,j) = 1, \ \ \ \ i=m\ \ \text{or}\ \ j=nf(i,j)=1, i=m or j=n
f(i,j)=f(i+1,j)+f(i,j+1) 1≤i<m,1≤j<nf(i,j) = f(i+1,j) + f(i,j+1)\ \ \ \ 1\leq i<m, 1\leq j <nf(i,j)=f(i+1,j)+f(i,j+1) 1≤i<m,1≤j<n
-
-
数字翻译为字符串 https://leetcode.cn/problems/ba-shu-zi-fan-yi-cheng-zi-fu-chuan-lcof/
-
分割回文 https://leetcode.cn/problems/palindrome-partitioning/
-

被折叠的 条评论
为什么被折叠?



