LIS(最长不下降/上升子序列)与LCS(最长公共子序列)

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]=\left\{ \begin{array}{rcl} f[i-1][j-1]+1&&{s1[i]=s2[j]}\\ min{(f[i-1][j],f[i][j-1])}&& {s1[i] \neq s2[j]}\\ \end{array} \right.

        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,二分

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值