动态规划:
(1)将大问题变为小问题,大问题的解决依赖小问题的答案,但集合间重合。这一点比较像分治算法,不过分治算法的各个区间互相独立,递归深度遍历时间复杂度(log2n)。
(2)先找出小问题的最优子集,继而找出大问题的最优子集 。这一点像回溯算法子集树,子集树也是寻找最优子集,不过子集树是通过递归,穷举出所有情况(2^n)然后再判断(虽然也有剪枝操作)。
(3)分析问题的方式:a.可以从上往下分析问题(大问题的解决依赖小问题的答案);也可以从下往上分析(归纳总结),得出公式f(n)=C1*f(n-1)+C2*f(n-2);后一点比较像贪心算法,因为贪心总结出的规律一般是发f(n) = C1*n^2+C2*n+C3。
总之,动态规划,适用范围比贪心广,空间复杂度比子集树低,相应的问题分治根本解决不了。那么,我们来总结一下动态规划的题集,主要关注动态规划的核心——辅助数组dp[]。
一. 01背包
有n个物品的重量是w[] = { 8,6,5,4,7 },相应的价值为v[] = { 6,9,7,8,2 },现有容纳重量为18的背包,那么它的最优价值选择是什么?(注:此题也可以用子集树来做)
- 辅助数组如何选择?
直接揭晓答案dp[18+1][5]。它是二维的,一维表示背包容量,一维表示可供选择的集合大小。举例dp[10][3]就表示背包容量为10,从前三项中选择出最优解。 - dp如何表示?
答案是dp[i][j] = max{dp[i][j-1], v[j]+dp[i-w[j]][j-1]}。我们认为dp[i][j-1]的值是,容量为i,前j-1项中选出的最优解,那么现在新加入第j项结果会如何?结果想要改变,那么最优解中就必须包括v[j],不然,岂不是和前j-1项的结果一样了,然后两种情况进行比较,谁价值大选谁。 - 代码
int main()
{
int w[] = { 8,6,5,4,7 };
int v[] = { 6,9,7,8,2 };
const int n = 5;
const int c = 18;
int dp[c + 1][n] = { 0 };
for (int i = 0; i <= c; ++i)
{
if (i >= w[0])
{
dp[i][0] = v[0];
}
}
for (int i = 1; i <= c; ++i)
{
for (int j = 1; j < n; ++j)
{
int tmp = 0;
if (i >= w[j])
{
tmp = v[j] + dp[i - w[j]][j - 1];
}
dp[i][j] = tmp > dp[i][j - 1] ? tmp : dp[i][j - 1];
}
}
cout << dp[c][n-1] << endl;
return 0;
}
二、两个字符串的重复子串和重复子序列
有字符串str1 = "helloworld"和str2 = “owoyuld”,求他们两者间重复的字串和子序列(注:字序列是可以不连续,而字串必须连续,且字串也可以用栈来做)
- 辅助数组的建立?
这里肯定是dp[m][n],一个字符串一维。 - 推到dp[x][y]等式
字串:dp[x][y] = str[x] == str[y] ? dp[x-1][y-1]+1 : 0
子序列: dp[x][y] = str[x] == str[y] ? dp[x-1][y-1]+1 : max{dp[x-1][y], dp[x][j-1]} - 代码
//字串
int LCS1(string str1, int m, string str2, int n, int **dp)
{
int maxVal = 0;
for (int i = 0; i < m; ++i)
{
for (int j = 0; j < n; ++j)
{
if (str1[i] == str2[j])
{
dp[i + 1][j + 1] = dp[i][j] + 1;
if (maxVal < dp[i + 1][j + 1])
{
maxVal = dp[i + 1][j + 1];
}
}
}
}
return maxVal;
}
//子序列
int LCS2(string str1, int m, string str2, int n, int **dp)
{
int maxVal = 0;
dp[0][0] = 0;
for(int i=1; i<m; ++i)
{
dp[i][0] = dp[i-1][0];
if(str1[i] == str2[0])
{
dp[i][0] += 1;
}
}
for(int j=1; j<n; ++j)
{
dp[0][j] = dp[0][j-1];
if(str2[j] == str1[0])
{
dp[0][j] += 1;
}
}
for (int i = 1; i < m; ++i)
{
for (int j = 1; j < n; ++j)
{
if (str1[i] == str2[j])
{
dp[i][j] = dp[i-1][j-1] + 1;
}
else
{
if (dp[i][j-1] > dp[i-1][j])
{
dp[i][j] = dp[i][j-1];
}
else
{
dp[i][j] = dp[i-1][j];
}
}
}
}
return dp[m-1][n-1];
}
- 子序列图