LIS
LIS即最长上升(不下降)子序列,最主要的是一个d数组,d[i]表示的是长度为i,也就是从1到i,第i位最小的值。
比如一个a数组={3,2,1,2},那么从前往后扫,一开始d[1]=3,因为长度为1的话,3是满足最小的条件,当指到2时候,进行维护,d[1]=2。也就是说,长度为1的话,最小的是2就可以满足了,同理,当a[i]=1时候,d[1]更新为1,因为长度为一,最后那一位是1即可满足。为什么会这样呢,接下来看下一位的2,它比d[1]要大,那么长度就+1,d[2]=2。
这个d[2]=2的意思就是,长度为2的最长不下降/上升子序列,最后一位是2即可满足,要是后面如果还有5的话,那么就会在2的基础上len++,d[3]=5;
我们易知,d数组是单调递增的,只有递增才能使得len++,不然就会替换最优解
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
d[1]=a[1];len=1;
for (int i=2;i<=n;i++)
{
if (a[i]>d[len]) d[++len]=a[i];
else
{
int t=upper_bound(d+1,d+1+len,a[i])-d;
//因为d是单调递增的,所以二分查找位置t
//最长上升子序列 不下降用lower_bound
d[t]=a[i];
}
}
cout<<len;
输出路径
如果是想要输出路径,我们需要将每一次的d[i]记录下来,只要有一次d[i]赋值(而且只有一次变化),都用一个pos[i]记录下来,如有一个数组a={8,3,4,2,6,5,6},那么经过循环得到pos[i]={1,1,2,1,3,2,4},这样我们从后往前,遇到的第一个当前的就记录到ans下,如{1,1,2,1,3,2,4}中标红的就是我们需要的答案,为什么倒数第二个的2没有用?那是因为你的3还没出现,3是无法继承倒数第二个的2的。
cin>>n;
for (int i=1;i<=n;i++) cin>>a[i];
d[1]=a[1];len=1;pos[1]=1;
for (int i=2;i<=n;i++)
{
if (a[i]>d[len]) d[++len]=a[i],pos[i]=len;
else
{
int t=upper_bound(d+1,d+1+len,a[i])-d;
//d单调递增
//最长上升子序列 不下降用lower_bound
d[t]=a[i];
pos[i]=t;
}
}
cout<<len<<endl;
int tmp=len,cnt=n;//tmp就是所说的当前的
while (tmp)
{
while (pos[cnt]!=tmp) cnt--;
ans[tmp]=a[cnt];
tmp--;
}
for (int i=1;i<=len;i++) cout<<ans[i]<<" ";
LCS
LCS也就是最长公共子序列,暴力肯定不是我们想要的,那么动态规划最重要的就是状态转移方程。
f[i][j]代表的是s1的前i个和s2的前j个的最长公共子序列的长度,不难解释,如果当前相等的话(s1[i]与s2[j]),那么就可以在之前(f[i-1][j-1])的基础上+1,如果不相等的话就从之前的最优解(f[i-1][j]与f[i][j-1])转移过来
cin>>s1>>s2;
l1=s1.length();l2=s2.length();
s1=" "+s1;s2=" "+s2;
for (int i=1;i<=l1;i++)
{
for (int j=1;j<=l2;j++)
{
if (s1[i]==s2[j]) f[i][j]=f[i-1][j-1]+1;
else
f[i][j]=min(f[i-1][j],f[i][j-1]);
}
}
cout<<f[l1][l2]<<endl;
输出路径
我们用一个steps数组来存当前状态是从哪转移过来的,我这里用1、2、3分别定义三种方向,1是从左边←(也就是f[i][j-1]),2是从左上↖(也就是f[i-1][j-1]),3是从上边↑(也就是f[i-1][j])。然后从后往前去递归寻找输出。
int main()
{
cin>>s1>>s2;
l1=s1.length();l2=s2.length();
s1=" "+s1;s2=" "+s2;
for (int i=1;i<=l1;i++)
{
for (int j=1;j<=l2;j++)
{
if (s1[i]==s2[j]) f[i][j]=f[i-1][j-1]+1,steps[i][j]=2;
else
{
if (f[i-1][j]>f[i][j-1]) f[i][j]=f[i-1][j],steps[i][j]=3;
else f[i][j]=f[i][j-1],steps[i][j]=1;
}
}
}
cout<<f[l1][l2]<<endl;
LCS(l1,l2);
return 0;
}
inline void LCS(int x,int y)
{
//3种状态
if (steps[x][y]==2)
{
LCS(x-1,y-1);
cout<<s2[y];
//cout<<s1[x];也是一样的
}
if (steps[x][y]==1) LCS(x,y-1);
if (steps[x][y]==3) LCS(x-1,y);
//递归寻找
}
最长公共子串
字串要求连续,那么如果不相等就不能继承转移,也就是上述的f[i][j]=0(如果不相等的话)
cin>>s1>>s2;
l1=s1.length();l2=s2.length();
s1=" "+s1;s2=" "+s2;
for (int i=1;i<=l1;i++)
{
for (int j=1;j<=l2;j++)
{
if (s1[i]==s2[j])
f[i][j]=f[i-1][j-1]+1,ans=max(ans,f[i][j]);
else
f[i][j]=0;
}
}
cout<<ans<<endl;
return 0;
最长回文子串
我们将字符串反,然后求最长公共子串,例如s=“cbbd”,我们反转得到st=“dbbc”,那么最长公共子串就是ans=“bb“,但是这个方法在leetcode里面会超时最后一个点。
我们采用动态规划,设置一层循环模拟长度l
cin>>st;
len=st.length();
if (len==0||len==1) {cout<<st;return 0;}
maxn=1;
for (int i=0;i<len;i++)
{
f[i][i]=1;
if (i+1<len&&st[i]==st[i+1]) f[i][i]=2,maxn=2,s=i;
}
//预处理,将长度为1和2的处理
for (int l=3;l<=len;l++)
//模拟长度
{
for (int i=0;i+l-1<len;i++)
{
int j=i+l-1;
if (st[i]==st[j]&&f[i+1][j-1]==1)
f[i][j]=1,s=i,maxn=l;
}
}
cout<<st.substr(s,maxn);
return 0;
还有一种方法是中心拓展法,具体参考最长回文子串 C++ - 最长回文子串 - 力扣(LeetCode) (leetcode-cn.com)
最长公共回文子串
占坑,Manacher算法(马拉车),hash,二分