一、动态规划的三大步骤
动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤,
如果你听不懂,也没关系,下面会有很多例题讲解,估计你就懂了。之所以不配合例题来讲这些步骤,也是为了怕你们脑袋乱了
第一步骤:定义数组元素的含义,上面说了,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?
第二步骤:找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]…..dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。
学过动态规划的可能都经常听到最优子结构,把大的问题拆分成小的问题,说时候,最开始的时候,我是对最优子结构一梦懵逼的。估计你们也听多了,所以这一次,我将换一种形式来讲,不再是各种子问题,各种最优子结构。所以大佬可别喷我再乱讲,因为我说了,这是我自己平时做题的套路。
第三步骤:找出初始值。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值。
由了初始值,并且有了数组元素之间的关系式,那么我们就可以得到 dp[n] 的值了,而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样,这道题也就解出来了。
下面我们写3道题:
公共子串,它的特点是必须是字串的元素必须是连续的
步骤一: `dp[i][j]`表示以`text1[i-1]`和`text2[j-1]`结尾的最大公共子串的长度。
如果两个字符不匹配,那么以这两个字符结尾的最大公共子串长度为`0`,因为子串必须是连续的。
步骤二 找出关系 分两种情况 :
1 当text1[i] == text2[j]时
dp[i][j] = dp[i-1][j-1]+1
以text1[i]和text2[j]结尾的字串的长度,比以text1[i-1]和text2[j-1]的字串长度大1
2 当text[i] != text2[j]时
dp[i][j] = 0;
因为字串必须是连续的,所以有一个元素不等,那就不算匹配
ans = max(f[i][j],ans);
因为最优的值不一定是dp[text1.size()][text2.size()] 即不一定都是结尾匹配的情况
步骤三 设置初始条件
dp[i][0] = 0 i = 0~text1.size();
dp[0][j] = 0 j = 0~text2.size();
显然一个元素为0的串,是没有最长字串的
或者说,最长字串为0
下面是代码
#include <iostream>
using namespace std;
string s, p;
int main() {
cin >> s;
cin >> p;
int n = s.size();
int m = p.size();
int f[1005][1005];
for (int i = 0 ; i <= n ; i++)
f[i][0] = 0;
for (int i = 0 ; i <= m ; i++)
f[0][i] = 0;
int maxx = -1;
for (int i = 1 ; i <= n ; i++)
for (int j = 1 ; j <= m ; j++) {
if (s[i - 1] == p[j - 1])
f[i][j] = f[i - 1][j - 1] + 1;
else
f[i][j] = 0;
maxx = max(maxx,f[i][j]);
}
cout<<maxx;
}
// 64 位输出请用 printf("%lld")
最长上升子序列是指,从原序列中按顺序取出一些数字排在一起,这些数字是逐渐增大的。
它的特点是,可以不连续
步骤一 f[i] 定义为,以a[i]为结尾的最长上升序列的长度。
步骤二 找出关系
1
i = 2~n; j = 1 ~ i;
j <= i
当 a[i] > a[j] 时,证明a[j]是一个可选为上升序列的元素
f[i] = max(f[i],f[j]+1)
2
当 a[i] == a[j] 时,没必要选a[j]
f[i] = f[i];
3
当 a[i] < a[j] 时,显然不满足上升序列的条件
不考虑
ans = max(ans,f[i])
步骤三 初始条件
i = 1 ~ n
f[i] = 1
每一个元素都是长度为1的上升子序列
最终代码
#include <iostream>
using namespace std;
int n,a[5005];
int main() {
int f[5005];
cin>>n;
for(int i = 1 ; i <= n ; i++)
cin>>a[i];
for (int i = 0 ; i <= n ; i++)
f[i]=1;
int maxx = -1;
for (int i = 2 ; i <= n ; i++)
for (int j = 1 ; j < i ; j++) {
if (a[j] < a[i])
f[i] = max(f[j]+1,f[i]);
maxx = max(maxx,f[i]);
}
cout<<maxx;
}
// 64 位输出请用 printf("%lld")
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
所以最长公共子序列不一定要连续
步骤一
定义f[i][j] 是 str1 前 i-1 个字符。和 str2 前 j-1 个字符的最长公共子序列
步骤二
找出关系
i = 1~str1.size()
j = 1~str2.size()
1
当str1[i-1] == str2[j-1]
f[i][j] = f[i-1][j-1]+1
最长子序列+1
2 当str1[i-1] != str2[j-1] 时
f[i][j] = max(f[i][j],f[i-1][j])
可以考虑删除str1[i],只考虑str1 前 i-1个字符
3 当str1[i-1] != str2[j-1] 时
f[i][j] = max(f[i][j],f[i][j-1])
可以考虑删除str2[j],只考虑str2 前 j-1个字符
4 当str1[i-1] != str2[j-1] 时
f[i][j] = max(f[i][j],f[i-1][j-1])
可以只考虑str1 前 i-1个字符 , 和str2 前 j-1个字符
ans = f[str1.size()][str2.size()]
即综合考虑str1和str2所有的字符后的答案
步骤三 初始条件
i = 1~str1.size()
j = 1~str2.size()
f[i][0] = 0;
f[0][j] = 0;
显然当一个序列元素个数为0时,其不可能与其他序列有公共序列
代码
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size();
int m = text2.size();
int f[1005][1005];
for(int i = 0 ; i <= n ; i++)
f[i][0] = 0;
for(int i = 0 ; i <= m ; i++)
f[0][i] = 0;
for(int i = 1 ; i <= n ; i++)
for(int j = 1 ; j <= m ; j++)
{
if(text1[i-1] == text2[j-1])
f[i][j] = f[i-1][j-1] + 1;
else
f[i][j] = max(f[i-1][j],max(f[i-1][j-1],f[i][j-1]));
}
return f[n][m];
}
};