动态规划回顾(二)线性dp

线性动态规划

一般都是对一维数组进行操作,比如求该序列里的上升子序列,下降子序列,或者是求两个序列里的公共子序列,说到底都是线性操作

1.最长上升(下降)子序列(单序列)

以最长上升为例,下降改变符合就行

01.暴力版 O ( n 2 ) O(n^2) O(n2)

我们一般设状态方程是 f [ i ] f[i] f[i] 表示以i结尾的最长上升子序列的长度
状态计算也是暴力,直接找前面小于该数的值中,最长的序列,然后拼接上去就行
最长上升子序列I

核心代码
for(int i=1;i<=n;i++)
    {
        f[i]=1;   // 初始化为一,保证以该数为底的数至少有一个
        for(int j=1;j<i;j++)
        {
            if(a[j]<a[i])
            {
                // 如果a[j]<a[i] 则把以a[j] 为结尾的数的序列长度+1和本身比较取最大值
                f[i]=max(f[i],f[j]+1); // 因为f[i]是用f[j] 更新的,而且i从小到大, // 一定记得要加1!!!!!
                                       // f[i]会记录以该点为最后一位的数的最长长度
            }
        }
    }
02.二分优化 O ( n l o g ( n ) ) O(nlog(n)) O(nlog(n))

当我们在找的时候会发现一个规律,当长度越长,结尾的数字也越大,有单调性
在这里插入图片描述
假如我们要枚举到4时,我们用二分找到第一个小于4的序列,我们可以用4去替代长度为3的序列,因为3结尾的序列长度是2,拼接上4就是3了,4的值比5小,比5更有可能接上其他数。两个数存的长度一样时,最后一位数小的数会更有用,因为阈值变大,可以存更多数
最长上升子序列II

核心代码
int len=0;   //  长度
    q[0]=-2e9;   // 标杆,0长度为无穷小
    for(int i=1;i<=n;i++)
    {
        int l=0,r=len;
        while(l<r)
        {
            int mid=l+r+1>>1;
            if(q[mid]<a[i]) l=mid;   // 找小于a[i]的数,且在右侧   该序列是从小到大
            						//  如果找最长下降子序列是直接 q[mid]>a[i] 因为该序列是从大到小,所以已经翻过了
            else r=mid-1;
        }
        len=max(len,r+1);  //  更新len,len就是答案
        q[r+1]=a[i];  //  因为q[r] 是小于a[i] 中最大的,q[r+1] 大于 a[i] 所以可以用a[i] 更新q[r+1] 
    }

2.双序列

这个模型就开始操作两个序列了

01.最长公共子序列

一般给两个序列,问其中最长的公共子序列的多长
状态表示
f [ i ] [ j ] f[i][j] f[i][j] 表示在第一个序列的前i个数字中,在第二个序列的前j个数字中,最长的公共子序列

状态计算
如果 a i ≠ b i a_i \not = b_i ai=bi
f [ i ] [ j ] f[i][j] f[i][j] f [ i − 1 ] [ j − 1 ] , f [ i − 1 ] [ j ] , f [ i ] [ j − 1 ] f[i-1][j-1] , f[i-1][j] , f[i][j-1] f[i1][j1],f[i1][j],f[i][j1]中的最大值转移过来

如果 a i = b i a_i = b_i ai=bi
f [ i ] [ j ] f[i][j] f[i][j]取上面三个状态,和 f [ i − 1 ] [ j − 1 ] + 1 f[i-1][j-1]+1 f[i1][j1]+1中的最大值转移过来

核心代码
for(int i=1;i<=n;i++)
        for(int j=1;j<=m;j++)
        {
            // 计算情况分为四种,a[i],b[j],不相等且和先前的字母也不相同即00,
            // a[i]和b[j-1]相同即为10,同理另一个是01,10。
            // 01,10,包含了00的情况,所以不用特判。
            // 这三种情况都是求他们先前的最大值。两个数不相等和相等是两个无关联的情况。
            // 所以先判这个不影响,如果相同再特判
            f[i][j]=max(f[i-1][j],f[i][j-1]);  
            // 这一步才是计数的,当他俩相同时,计数加上1,而且同一层j往后都会保留这个数
            // 并且在同一层循环中,数不会在变
            // 因为如果再遇见相同的数,都是f[i-1][j-1]+1,数值不变
            if(a[i]==b[j]) f[i][j]=max(f[i][j],f[i-1][j-1]+1);
        }
02.最短编辑距离

给我们两个字符串,让我们修改,可以增,删,改,求,把第一个变成第二个字符串的最小编辑次数

状态表示
f [ i ] [ j ] f[i][j] f[i][j] 表示第一个序列的前i个字母和第二个序列的前j个字母匹配的最短的编辑距离
状态计算
考虑状态转移的时候
先考虑如果我没有进行这个操作应该是什么状态
然后考虑你进行这一步操作之后会对你下一个状态造成什么影响
然后再加上之前状态表示中你决策出来的那个DP属性
这样就可以自然而然地搞出来转移方程啦

1)删除操作:把a[i]删掉之后a[1i]和b[1j]匹配
所以之前要先做到a[1(i-1)]和b[1j]匹配
f[i-1][j] + 1
2)插入操作:插入之后a[i]与b[j]完全匹配,所以插入的就是b[j]
那填之前a[1i]和b[1(j-1)]匹配
f[i][j-1] + 1
3)替换操作:把a[i]改成b[j]之后想要a[1i]与b[1j]匹配
那么修改这一位之前,a[1(i-1)]应该与b[1(j-1)]匹配
f[i-1][j-1] + 1
但是如果本来a[i]与b[j]这一位上就相等,那么不用改,即
f[i-1][j-1] + 0
一篇不错的题解
最短编辑距离

核心代码
for(int i=0;i<=m;i++) f[0][i]=i;  // 初始化
  for(int i=0;i<=n;i++) f[i][0]=i;
  
  for(int i=1;i<=n;i++)
  {
      for(int j=1;j<=m;j++)
      {
          f[i][j]=min(f[i-1][j]+1,f[i][j-1]+1);  // 有三种计算,先按照前两种,删和增算
          // 因为这两个和改是没有联系的,就是有改,取他们的最小值
          if(a[i]==b[j]) f[i][j]=min(f[i][j],f[i-1][j-1]);
          else f[i][j]=min(f[i][j],f[i-1][j-1]+1);
          
      }
  }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值