题目描述:
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
不成文的小规则:牢记子序列默认不连续,子数组默认是连续的!!!
解法1动态规划:
分析:
当我们遇到最值问题,无脑动态规划就完了,而动态规划最重要的一点就是要想好遍历方向和dp保存的信息是什么即可
运用动态规划的DP二维数组dp[i][j]表示以A[i-1]与B[j-1]结尾的公共子串的长度,子串必须以A[i-1]与B[j-1]结尾,一定要清楚dp数组的定义!!!这样才能更高效的得到dp数组的递推公式
刚开始定义dp[i][j]时,我想的是dp[i][j]表示从A[i-1]到B[j-1]的最长子串,在示例中最后返回dp[5][5],但是这个定义无法推出来dp[i][j]的递推公式,所以这个定义想法是错误的!!!
动态规划DP数组的具体思路:
单看 A 、B数组的最后一项,如果它们俩不一样,公共子序列不包括它们俩—— 以它们俩为末尾项形成不了公共子序列:dp[i][j] = 0
如果他们俩一样,以它们俩为末尾项的公共子序列,长度至少为 1 —— dp[i][j] 至少为 1,考虑它们俩前面的序列【能为它们俩提供多大的公共长度】—— dp[i-1][j-1]
它们俩的前缀序列的【最后一项】不相同,即它们的前一项不相同,前缀序列提供的公共长度 为 0 —— dp[i-1][j-1] = 0
以它们俩为末尾项的公共子序列的长度 = 0 + 1 = 1 —— dp[i][j] = 0 + 1 = 1
如果它们俩的前缀序列的【最后一项】相同
前缀部分能提供的公共长度—— dp[i-1][j-1] ,至少为 1
加上它们俩本身的长度 1 ,就是以它们俩为末尾项的公共子序列的长度 —— dp[i][j] = dp[i-1][j-1]
这样一来就是典型的动态规划:大问题与子问题相关,所以子问题的结果要用DP数组来保存
代码部分:
只要分析得到DP数组的定义以及DP数组的递推关系代码是相当简单的,无需过多解释!
class Solution{
public:
int findLength(vector<int>& A, vector<int>& B)
{
if(A.size() ==0||B.size() ==0) return 0;
vector<vector<int>> dp;
dp.resize(A.size()+1);
for(int i=0;i<A.size()+1;i++)
{
dp[i].resize(B.size()+1);
}
int result = 0;
for(int i=1;i<=A.size();i++)
{
for(int j = 1;j<B.size();j++)
{
if(A[i-1] == B[j-1])
{
dp[i][j] = dp[i-1][j-1]+1;
result = max(result,dp[i][j]);
}
}
}
return result;
}
};
时间复杂度O(NM)
空间复杂度O(NM) //N表示A数组长度,M表示B数组长度
解法2滑动窗口:
分析:
这个题如果我们用暴力求解的方法,时间复杂度最坏程度会达到O(n*3),原因时因为我们在比较的时候会出现很多重复的比较,例如:A=1,2,3,4,5,6 B=1,2,3,5,6 我们会在A=1时比较了A=1,B=1与A=2,B=2与A=3,B=3与A=4,B=5,然后我们在A=2时又会比较A=2,B=2与A=3,B=3与A=4,B=5,加粗部分就是我们重复比较的地方,这些重复比较都是没有意义的,因为,第二种情况的最长重复子数组一定是小于第一种情况的。
在处理字符串问题时我们可以首先想一下滑动窗口是否能够解决问题,
我们可以用左右手的食指来模拟两个数组串,一个在上一个在下,想象两根手指左右滑动,将其两个数组错开,当两组数组的”相同位置“(就是图中AB两个数组上下对等的两个数)上连续相同的数的个数即为这种情况下的最长子数组长度,我们可以一直比较,直到两数组上下没有相交的数时,这样我们每次比较的数就是在A中和B中“位置相同“的数,时间复杂度为O((N+M)*min(N,M)),所以我们要设计一个另类的比较函数–maxLength函数,比较AB“相同位置”的数;
代码:
class Solution{
int maxLength(vector<int>& A, vector<int>& B, int addA, int addB, int len)
//传入的len是需要比较的长度,也就是两个数组相交的长度,i表示A数组从i开始,0表示B数组从0开始
{
int ret = 0;int k = 0;
for (int i = 0; i < len; i++)
{
if (A[addA + i] == B[addB + i])//比较两数组错开后的相同位置的数是否相等
{
k++;
}
else
{
k = 0;
}
ret = max(k,ret);
}
return ret;
}
int findLength(vector<int>& A, vector<int>& B) {
int n = A.size(), m = B.size();
int ret = 0;
//B不动,A开始滑动
for (int i = 0; i < n; i++) {
int len = min(m, n - i);
int maxlen = maxLength(A, B, i, 0, len);
ret = max(ret, maxlen);
}
//为什么A不动B滑之后还要B不动A滑再来一次?因为如果只有一个动会少掉许多有效的比较情况,例如 [0,0,0,0,1]
// [1,0,0,0,0]
//A不动,B开始滑动
for (int i = 0; i < m; i++) {
int len = min(n, m - i);
int maxlen = maxLength(A, B, 0, i, len);
ret = max(ret, maxlen);
}
return ret;
}
};
时间复杂度O((N+M)*(min(N,M)))
空间复杂度O(1)