动态规划(二)
再列举一些经典的动态规划习题。
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 { |