些许的灵感:我们可以从dp数组的最后一位开始考虑 ,看看这个是怎么从前面推来的,这样就方便发现子问题。如果知道子问题的值,那么就好办了。
动态规划更多的是一种递推的思想(从后往前推,这样可以避免递归)
动态规划的状态转移方程就是由三个词决定的:选定 继承 优化
每个数组元素都是我们要的答案(一般情况来说)。
关于数据结构:对于这种有两个线性结构的来说一般是采用二维数组(线性动态规划一般使用二维数组)。
最优情况下考虑创造值。
非最优情况下考虑继承(而且是在继承中取最优)。
子问题就是相当于初始你是怎么想的。追溯到子问题那么这个问题就会结束。
为什么我们要用一个二维数组来存数据:i代表s1串,j代表s2串,每一个dp数组所存的元素就是lis的长度(就是我们最后需要的答案,那么数组的最后就是我们呢需要的答案)
为什么相等的时候就要取dp[i-1][j-1]+1?:因为dp[i-1][j-1]就是我们之前算过的最长公共子序列,由于已经满足了这两个相等,所以要向上一个取最优解。
为什么不相等的时候要去max(dp[i-1][j],dp[i][j-1])?:由于我们没有发现相等的,所以要么向s1前找最优解,要么向s2找最优解。i-1就是相当于向s1前面找最优解,j-1相当于向s2找最优解。
在线性动态规划中选取数字这个操作很重要,就是以这个数字为标准(这句话我说出来可能比较抽象)。
下面这张图方便理解
模板】最长公共子序列
dp数组的含义:下标i代表前i个数字(包括i),下标j代表前j个数字(包括j) ,dp数组所存的数字就是长度为i和j的时候的lis长度。
动态规划代码(时间会爆)
#include<iostream>
#include<cstdio>
using namespace std;
const int N=1e5+100;
int s1[N],s2[N];
int dp[100][100];
int main()
{
int n;
cin>>n;
for(int i=1;i<=n;i++)cin>>s1[i];
for(int i=1;i<=n;i++)cin>>s2[i];
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s1[i]==s2[j])
{
dp[i][j]=dp[i-1][j-1]+1;
}
else
{
dp[i][j]=max(dp[i-1][j],dp[i][j-1]);
}
}
}
// for(int i=1;i<=n;i++)
// {
// for(int j=1;j<=n;j++)
// {
// cout<<dp[i][j]<<" ";
// }
// cout<<endl;
// }
cout<<dp[n][n]<<endl;
return 0;
}
编辑距离
做了前面那道题这一道题就不是很难理解了(虽然还是很难理解)
dp数组的含义:
下标i就是s1的前i个字符(包括i),下标i就是s2的前j个字符(包括j)。
对于dp数组里面存的数字就是我们要的答案:所要进行的最少操作
在前面的灵感中我就已经说过,动态规划其实就是一种递推的过程,所以我们一般从从往前的思路进行思考,我们们选定最后一个字符进行继承优化。
子问题看似是三个其实是四个:
首先就是三个题目给的(详细在注释中认真看注释)
// //插入操作就是相当于s1的前i个字符和s2的j-1个字符具有高度相似性,所以可以继承前面的
dp[i][j]=dp[i][j-1]+1;
// //删除操作就是相当于s1的前i-1个字符和s2的前j个字符具有高度相似性,所以可以继承前面的
dp[i][j]=dp[i-1][j]+1;
// //替换操作就是s1的前i-1个字符和s2的前j-1个字符具有高度相似性,所以可以继承前面的
dp[i][j]=dp[i-1][j-1]+1;
最后一个操作就是如果相同就不需要进行任何操作,将i-1和j-1前面的操作步数直接继承过来就行了
dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);
那我们如何对删减 替换 增加呢?
其实不用想这么多,那个步数少就继承那个,所以直接用min函数将这三种情况选择最小的即可
dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);
完整的代码如下:
#include<iostream>
#include<cstring>
#include<cstdio>
#include<string.h>
using namespace std;
char s1[2010];
char s2[2010];
int dp[2010][2010];
int main()
{
scanf("%s",s1+1);
scanf("%s",s2+1);
int len1=strlen(s1+1);
int len2=strlen(s2+1);
dp[0][0]=0;
for(int j=1;j<=len2;j++)
{
dp[0][j]=j;
}
for(int i=1;i<=len1;i++)
{
dp[i][0]=i;
}
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
//继承,不需要进行任何操作
if(s1[i]==s2[j])dp[i][j]=dp[i-1][j-1];
if(s1[i]!=s2[j])
dp[i][j]=min(min(dp[i][j-1]+1,dp[i-1][j]+1),dp[i-1][j-1]+1);
// //插入操作就是相当于s1的前i个字符和s2的j-1个字符具有高度相似性,所以可以继承前面的
// dp[i][j]=dp[i][j-1]+1;
// //删除操作就是相当于s1的前i-1个字符和s2的前j个字符具有高度相似性,所以可以继承前面的
// dp[i][j]=dp[i-1][j]+1;
// //替换操作就是s1的前i-1个字符和s2的前j-1个字符具有高度相似性,所以可以继承前面的
// dp[i][j]=dp[i-1][j-1]+1;
}
}
// for(int i=1;i<=len1;i++)
// {
// for(int j=1;j<=len2;j++)
// printf("%d ",dp[i][j]);
// cout<<endl;
// }
cout<<dp[len1][len2]<<endl;
return 0;
}