动态规划(二)
再列举一些经典的动态规划习题。
1. 最长回文子串
这是LeetCode 第5号问题。给定一个字符串 s
,找到 s
中最长的回文子串。你可以假设 s
的最大长度为 1000。
思路:dp[i][j]
表示s[i:j]
是否为一个回文串,可知i <= j,dp[i][i] = true
,当s[i] == s[j]
时,s[i:j]
是否是一个回文串就由s[i+1:j-1]
来决定了,当s[i]!=s[j]
,这是s[i:j]
一定不是回文串。由此可以写出状态转移方程。
注意:需要注意的是,写循环的时候很容易弄混内外循环,以下两种枚举方式都是可行的。
以字符串"babab"
为例,图解两种枚举方式状态如何转移。
第一种方式(PPT作图真好用啊)。解释一下,以dp[0][4]
为例,此时s[0] == s[4] == 'b'
,这时需要判断dp[1][3]
(即子串"aba"
)是不是回文串,由于上一次循环已经得出dp[1][3]
的结果是true,因此dp[0][4]
结果也为true,并且长度为5(箭头表示这张表是从左到右,自上而下填写的)。
1 | class Solution { |
另一种递推方式。其实是在判断dp[j][i]
是不是回文串。
代码如下:
1 | class Solution { |
2. 整数划分系列
2.1 整数划分1
将一个正整数划分为不大于自身的一系列正整数之和,给定$n$,求一共有多少种不同的划分方案。
该题可以抽象为:给定一个容量为 n 的背包,有体积为1,2,3,4…,n的物品,求装满背包有多少种不同的装法。每个物品可以无限使用。与完全背包不同的是,完全背包最后求的是背包可以得到的最大价值,而本题求得是方案数。
首先先定义状态,和背包问题类似,我们可以定义dp[i][j]
为使用前i
个物品,装满容量为j
的背包的方案数,所求答案即:dp[n][n]
。对于每一个物品我们都可以选择装和不装;如果装入第i
个物品,dp[i-1][j-i]
,前提是j >= w[i] = i
,如果不装,dp[i-1][j]
,因此若j >= i,dp[i][j] = dp[i-1][j-i] + dp[i-1][j]
,当j < w[i] = i
,dp[i][j] = dp[i-1][j]
,此时第i
个物品不可用。注意初始化问题,体积为0时只有一种方案:啥也不装。
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=1028
1 |
|
状态压缩:
1 |
|
2.2 整数划分2
给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。这是LeetCode 343号问题。
先这么想:设 f(n) 表示 n 拆分成至少2个整数的和的最大乘积。即
,还有一种情况就是直接拆分成2个数的和,即$j*(i-j)$。可以看出这是递归,可以用自顶向下+记忆化解决,也可以自底向上通过递推解决。
1 | class Solution { |
注意:当n过于大时,这时就应该考虑采用数学方法求解了。
2.3 整数划分3
将整数n分成k份,且每份不能为空,例如:n=7,k=3,下面三种分法被认为是相同的。1,1,5; 1,5,1; 5,1,1;
问有多少种不同的分法。题目链接:https://vijos.org/p/1117
1 |
|
3. 零钱兑换
3.1 零钱兑换 I
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。你可以认为每种硬币的数量是无限的。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/coin-change
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
思路分析:
同样的,这个问题也可以抽象成一个背包问题,背包容量为amount,物品价值为coins
,状态dp[j]
表示组成金额j
时需要的最小硬币个数,dp[j] = min(dp[j], dp[j - coins[i]] + 1)
,dp[j-coins[i]]
表示使用第i
个硬币来组成金额j
,那么所需硬币个数就需要在dp[j-coins[i]]
的基础上加1。
1 | class Solution { |
3.2 零钱兑换 II
给定不同面额的硬币和一个总金额。写出函数来计算可以凑成总金额的硬币组合数。假设每一种面额的硬币有无限个。 这是LeetCode第518号问题。
看懂了吗,这个题是不是和第一个整数划分问题一摸一样!!!
1 | class Solution { |
4. 最大正方形
在一个由 '0'
和 '1'
组成的二维矩阵内,找到只包含 '1'
的最大正方形,并返回其面积。LeetCode第221号问题(https://leetcode-cn.com/problems/maximal-square/)。
需要注意的是状态转移中取的是min
,类似木桶原理,装水的多少由最短的那个木头决定。
1 | class Solution { |
5. 交错字符串
给定三个字符串 s1
、s2
、s3
,请你帮忙验证 s3
是否是由 s1
和 s2
交错 组成的。
即s3
中属于s1
的字符顺序构成s1
,属于s2
的字符顺序构成s2
。
关于字符串的动态规划题还是挺有意思的。题解见下图:
给定:s1: “abc”,s2:”ead”,s3: ”aeabcd”
表格中$(i,j)$位置表示s1[:i-1],s2[:j-1]
是否可以交错构成s3[:i+j-1]
。
当 $i=1.j=1$ 为例,$(1,1)$ 表示s1
第一个字符与s2
第一个字符是否可以交错构成s3[:1]
即前两个字符,进行判断:如果 s3[1] == s1[0]
,那么当前状态由 dp[i-1][j]
决定,即''
与s2[0]
是否可以构成s3[0]
(转化为子问题),如果 s3[1] == s2[0]
,那么转求dp[i][j-1]
,即s1[0]
与''
是否可以构成s3[0]
。可见s2
第一个字符e
与s3
第二个字符相同,那么就判断空字符''
与s1
第一个字符a
是否可以构成s3
第一个字符a
,明显可以,因此dp[1][1]
为True。其余情况类同。
1 | class Solution { |