动态规划思路
要用动态规划要具备两个性质,重叠子问题和最优子问题
- 最优子问题:如果一个问题的最优解包含了其中子问题的最优解,那么称其有最优子结构
- 重叠子问题:当解决一个问题时,往往依赖其更小规模的子问题的解,甚至依赖与若干个规模更小的子问题的解。
如果一个问题满足这两个条件,就可以用动态规划的方法去求解。对于动态规划问题,最难的就是列出状态转移方程。可以参考下面的方式来分解问题:
明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组 / 函数的含义
动态规划题目
最长回文子串
问题描述
leetcode #5
给定一个字符串 s,找到 s 中最长的回文子串。你可以假设 s 的最大长度为 1000。
示例 1:
输入: “babad”
输出: “bab”
注意: “aba” 也是一个有效答案。
示例 2:
输入: “cbbd”
输出: “bb”
解题思路
这一题里面的状态 dp[i][j] 代表了字符串 s[i:j] 是否是回文子串。初始的状态 dp[i][i] 都为 true,dp[i][i+1] 则要判断下字符 s[i] 和 s[i+1] 是否相等。
状态转移方程则为:
dp[i][j] = dp[i+1][j-1] && s[i] == s[j]
这里需要注意的一个点是,遍历不能直接用 i 和 j 遍历,需要用子字符串的长度来作为第一层循环,起始字符的位置作为第二层循环。
1 | class Solution { |
不过这样也还是超时了,这题也太容易超时了吧。
子集和问题
问题描述
给定一个正整数集合 S 和一个正整数 M,是否在 S 中存在子集使得子集之和等于 M。
例如:
[1,2,6,3,17,82,23,234] -> 26
Solution [0,1,6]
[1,2,6,3,17,82,23,234] -> 40
Solution [4,6]
[1,2,6,3,17,82,23,234] -> 23
Solution [6]
解题思路
对于这个问题,难点同样在于列出状态转移方程。首先我们应该先明确状态。对于一个集合 S=a1,a2,…,an 中的每一个元素,都存在两种状态取和不取,再考虑他们的和是否等于 M,但是这样的情况就有 2n。所以要换个思路,令 dp[i][j] 代表前 i 个元素中是否存在子集使得子集和等于 j。那么对于 dp[i][j] 则有两种情况:
- 如果 S[i] > j,那么 i 一定是不再子集中的,所以 dp[i][j]=dp[i-1][j],即当前数大于子集和的目标数,那么当前数对于子集和能否等于目标数没有影响,所以当前状态(dp[i][j]) 应该是等于不包含 S[i] 时的状态(dp[i-1][j])。
- 如果 S[i] <= j,也存在两种情况:S[i] 不在子集中,那么 dp[i][j]=dp[i-1][j];如果 S[i] 在子集中,那么它的状态应该和上一个状态并且目标数等于 j-S[i] 时的状态相等,即 dp[i][j] = dp[i-1][j-S[i]]。
所以可以列出状态转移方程:
1 | if (S[i] > j) |
程序如下:
1 | vector<vector<bool> > dp(nums.size()+1, vector<bool> (target+1, false)); |
如果想要找到对应的子集中的元素的序号,那么可以通过以下方法:
首先我们知道如果 dp[i][j]=dp[i-1][j]=true,那么这代表 S[i] 不在子集中;但是如果 dp[i][j]=true,[i-1][j]=false 那么 S[i] 则一定在子集中,那么我们就可以找到一个子集中的元素 S[i] 了。又因为 S[i] 在子集中,那么如果排除掉 S[i],剩下的需要求的子集和就变成 j-S[i],所以我就接着从 dp[i-1][j] 继续搜索子集中的元素了。
最后把这两块连起来:
1 | vector<int> sum_problem(vector<int> nums, int target){ |
参考资料
https://labuladong.gitbook.io/algo/dong-tai-gui-hua-xi-lie/dong-tai-gui-hua-xiang-jie-jin-jie
https://www.bilibili.com/video/av38722679
子集和问题:
https://www.cnblogs.com/yulinfeng/p/7106564.html
https://zhuanlan.zhihu.com/p/37822898