动态规划经典例题:最长公共子序列相关 、滑动窗口方法

25 篇文章 2 订阅

最长公共子序列,可以拓展到非字符串的结构,如数组等,毕竟其实字符串也可以理解为字符数组

例题:

给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。

示例0:

输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

 

方法1:动态规划:时间复杂度O(N*M),空间复杂度:O(N*M),N和M分别为两个数组各自的长度

思路:   使用max记录最大的长度,dp数组记录dp[i][j] 结尾的子数组的重复长度,这里的动态转移方程写的比较简单,相当于只是对局部最大重复长度的记录(或者说是对A数组i位置开始对B数组j位置开始的数组的最长公共前缀的计算)

所以需要一个max记录全局最优

              if(i==0||j==0)//边界处理
              {
                 dp[i][j]=A[i]==B[j]?1:0;
              }
              else if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;
              else dp[i][j]=0;
              if(dp[i][j]>max) max=dp[i][j];

class Solution {
public:
    int findLength(vector<int>& A, vector<int>& B) {
         if(A.size()==0||B.size()==0) return 0;
       int dp[A.size()][B.size()];//记录以i,j结尾的子数组的重复长度
       int max=0;
        for(int i=0;i<A.size();++i)
          for(int j=0;j<B.size();++j)
          {
              if(i==0||j==0)//边界处理
              {
                 dp[i][j]=A[i]==B[j]?1:0;
              }
              else if(A[i]==B[j]) dp[i][j]=dp[i-1][j-1]+1;
              else dp[i][j]=0;
              if(dp[i][j]>max) max=dp[i][j];
          }

    return max;
    }
};

方法2:使用滑动窗口,时间复杂度:O(N+M)×min(N,M))。,空间复杂度O(1)

概念 :滑动窗口算法可以用以解决数组/字符串的子元素问题,它可以将嵌套的循环问题,转换为单循环问题,降低时间复杂度

以下摘录自知乎用户:https://www.zhihu.com/question/314669016

即可以用O(N)的时间复杂度解决最大/最小子序列求和,乘积等问题

时间复杂度是 O(s + t)

示例3:

 

本题示例0的滑动窗口写法: 时间复杂度:O(N+M)*min(N,M) 空间复杂度O(1)

 

主要思路:

首先是暴力法:需要三层循环:

for(int i=0;i<n;i++)
{
    for()
    {//每次找到B中与A[i]相等的开始判断重复子数组长度,相当于是主动去找对齐的过程,需要多次对齐
        for()
        {

        }
    }
}

而滑动窗口的方法:思路上是默认就是对齐的,相当于用两次单层循环实现双层循环的功能或者说是两次双层循环,实现了三层循环的功能

重要的两个地方在于滑动窗口长度的确定,和是否还需要继续滑动的判断(例如当前最大长度是4,但是滑动窗口长度是3,那肯定不需要对滑动窗口进行判断了)

for(int i=0;i<n;i++)
{
    for(int j=0;j<m;j++)
    {//默认当前i与j对齐,来记录重复数组的长度
            len=min(m,n-i);//当前滑动窗口的长度
    }
}
for(int j=0;j<m;j++)
{
    for(int i=0;i<n;i++)
    {//默认当前i与j对齐,来记录重复数组的长度
             len=min(n,m-j);//当前滑动窗口的长度

    }
}

为什么需要从左到右和从右到左两次滑动,是因为

举个例子:

A:1,2,3,2,1

B:3,2,1

从左到右:(A固定,B滑动对齐)

1,2,3,2,1

3,2,1

 

1,2,3,2,1

   3,2,1

 

1,2,3,2,1

      3,2,1

 

从右到左:(B固定,A滑动对齐)

3,2,1

1,2,3,2,1

3,2,1
   1,2,3,2,1

3,2,1
      1,2,3,2,1

 

可以看出有些情况一个方向的滑动并不能找到最长的重复子数组,因为没有考虑到所有可能性

 

相当于使用滑动窗口,模拟了两个数组对齐的过程

class Solution {
public:

    int sonMax(vector<int>& A,int Abegin, vector<int>& B,int Bbegin,int len) //计算Abegin开始和Bbegin开始的最长子数组
//这里相当于O(n)的操作替代了双层循环
    //len表示窗口的长度
    {
        int re=0,max=0;
        for(int i=0;i<len;++i)
        {
            if(A[Abegin+i]==B[Bbegin+i]) re++;
            else re=0;
            if(re>max) max=re;
        }
        return max;
    }
    int findLength(vector<int>& A, vector<int>& B) {
        int len=0,max=0,re=0;
        for(int i=0;i<A.size();i++)
           { 
               len=min(B.size(),A.size()-i);//当前滑动窗口的长度
               if(len<max) break;
               re=sonMax(A,i,B,0,len);
               if(re>max) max=re;  
           }

        for(int i=0;i<B.size();i++)
           {
                len=min(A.size(),B.size()-i);
                if(len<max) break;                
                re=sonMax(A,0,B,i,len);
                if(re>max) max=re;  
           }
    return max;
    }
};

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

溯夜流云

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值