解决动态规划类问题的首要任务是找到最优子结构,典型的有台阶的走法问题:有一座高度是10级台阶的楼梯,从下往上走,每跨一步只能向上一级或两级台阶,要求计算一共有多少种走法。解决方法是:第一步,找到最优子结构:如果只差最后一步走到第十阶,这时有几种情况。然后根据最优子结构找到状态转移公式和边界条件,最后可以用动态规划求解。
以下是几个例子,用以练习动态规划类问题。
题目:输出最长上升子序列的个数,例如,给定数组{2,7,1,5,6,4,3,8,9},输出最长上升子序列的长度
思路:定义数组d[],d[i]表示以a[i]结尾的最长上升子序列,则所有d[i]中最大的值即为所求值。
代码:
#include
using namespace std;
int main()
{
int n;
while(cin >> n)
{
int *p = new int[n];
for (int i = 0; i < n; i++)
cin >> p[i];
int len = 1;
// 定义d[i]
int *d = new int[n];
// 动态规划过程
for (int i = 0; i < n; i++)
{
d[i] = 1;
for (int j = 0; j < i; j++)
{
if (p[j] < p[i] && d[j]+1 > d[i])
d[i] = d[j] + 1;
}
if (d[i] > len)
len = d[i];
}
cout << len << endl;
delete[]p;
delete[]d;
}
system("pause");
return 0;
}
题目:已知6种物品和一个可容纳60重量的背包,物品重量 w = {15,17,20,12,9,14},价值为 v = {32,37,46,26,21,30},问如何装包使得总价值最大。
思路:每个物品可以分为装入和不装入,由此可以得到最优子结构和迭代公式:
代码:
#include
#include
#include
using namespace std;
int main()
{
int n = 6;
int c = 60;
int w[] = {15,17,20,12,9,14};
int v[] = {32,37,46,26,21,30};
vector> f(7,vector(61,0));
for (int i = 0; i <= 60; i++)
{
f[0][i] = 0;
}
for (int i = 0; i <= 6; i++)
{
f[i][0] = 0;
}
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= 60; j++)
{
if(w[i-1] > j)
f[i][j] = f[i-1][j];
else
f[i][j] = max(f[i-1][j],v[i-1] + f[i-1][j-w[i-1]]);
}
}
cout << f[6][60] << endl;
system("pause");
return 0;
}
题目:
对于不同的字符串,我们希望能有办法判断相似程度,我们定义了一套操作方法来把两个不相同的字符串变得相同,具体的操作方法如下:1 修改一个字符,如把“a”替换为“b”。2 增加一个字符,如把“abdd”变为“aebdd”。3 删除一个字符,如把“travelling”变为“traveling”。比如,对于“abcdefg”和“abcdef”两个字符串来说,我们认为可以通过增加和减少一个“g”的方式来达到目的。上面的两种方案,都只需要一次操作。把这个操作所需要的次数定义为两个字符串的距离,而相似度等于“距离+1”的倒数。也就是说,“abcdefg”和“abcdef”的距离为1,相似度为1/2。给定任意两个字符串,你是否能写出一个算法来计算出它们的相似度呢?
思路:令dp[m+1][n+1]表示状态转移矩阵,dp[i][j]表示字符串str1[0...i-1]到str2[0..j-1]的最小编辑代价,则可以得到以下四种规则:
(1) dp[0][0]表示空串编辑成空串,故代价为dp[0][0] = 0;
(2) dp[i][0] = i;
(3) dp[0][j] = j;
(4) 其他情况下的 dp[i][j] 可以由边界条件推出,分为以下四种情况:
① str1[i] 先编辑为 str1[i-1],再由 str1[i-1] 编辑为 str2[j],dp[i][j] = dp[i-1][j] + 1;
② str1[i] 先编辑为 str2[i-1],再由 str2[i-1] 编辑为 str2[j],dp[i][j] = dp[i][j-1] + 1;
③ if : str1[i-1] == str2[j-1],dp[i][j] = dp[i-1][j-1];
④ if : str1[i-1] != str2[j-1], dp[i][j] = dp[i-1][j-1] + 1;
以上四种情况中最小的 dp[i][j] 即为所求。
代码:
#include
#include
#include
using namespace std;
int main()
{
string str1,str2;
while (cin >> str1 >> str2)
{
vector>dp (str1.length()+1,vector(str2.length()+1,0));
dp[0][0] = 0;
for (int i = 0; i <= str1.length(); i++) dp[i][0] = i;
for (int j = 0; j <= str2.length(); j++) dp[0][j] = j;
for (int i = 1; i <= str1.length(); i++)
{
for (int j = 1; j <= str2.length(); j++)
{
int dp1 = dp[i-1][j]+1;
int dp2 = dp[i][j-1]+1;
int dp3 = dp[i-1][j-1];
if(str1[i-1] != str2[j-1])
dp3++;
dp[i][j] = min(dp1,min(dp2,dp3));
}
}
cout << "1/" << dp[str1.length()][str2.length()]+1 << endl;
}
system("pause");
return 0;
}
参考链接:
漫画:什么是动态规划?
动态规划系列问题-最小编辑代价