最长公共子序列,可以拓展到非字符串的结构,如数组等,毕竟其实字符串也可以理解为字符数组
例题:
给两个整数数组 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;
}
};