最长公共子序列和最长公共子串都是dp的经典问题,是一定需要能随时写出来的。昨天大疆的笔试考到了,想到自己还没有刷到这题,因此今天就来分析总结一下这个问题的解法。leetcode上这题不是公共子串而是重复子数组,但是实际上是一样的。因为有leetcode方便测试代码,所以下面的代码是最长重复子数组的,换成串也一样。
1. 题目描述
给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
示例 1:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出: 3
解释:
长度最长的公共子数组是 [3, 2, 1]。
2. 解题思路
这是一道经典的动态规划的题目,能用动态规划来解说明存在最优子问题。假设在A数组中的i位置和B数组的j位置往前长度为L的子数组是相同的,那么只要A中i+1的位置和B中j+1的位置的元素相同,那么重复子数组的长度就可以递增1。这就是这个问题能用dp解的理论基础。因此只需要从最简单的情况i=0和j=0的情况就可以向后递推得到任意i,j上的重复子数组的长度,最后只需要取出最长的即可。
递推形式确定了,那么dp算法就很好写了。只需要照着之前总结的dp写法写就很容易写出完整的代码。
照着dp的写法步骤代码如下:
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int lenA = A.size();
int lenB = B.size();
if(lenA == 0 || lenB == 0)
return 0;
//申请dp二维数组
vector<vector<int>> dp(lenA, vector<int> (lenB, 0));
int maxLen = 0;
int maxEnd = 0;
//初始化边界条件
for(int i = 0; i < lenA; i++){
if(A[i] == B[0]){
dp[i][0] = 1;
maxLen = 1;
maxEnd = i;
}
}
for(int j = 0; j < lenB; j++){
if(A[0] == B[j]){
dp[0][j] = 1;
maxLen = 1;
maxEnd = 0;
}
}
//递推
for(int i = 1; i < lenA; i++){
for(int j = 1; j < lenB; j++){
if(A[i] == B[j]){
dp[i][j] = 1 + dp[i - 1][j - 1];
if(dp[i][j] > maxLen){
maxLen = dp[i][j];
maxEnd = i;
}
}
else{
dp[i][j] = 0;
}
}
}
return maxLen;
}
};
leetcode这一题只需要返回最长的长度,而不需要返回重复的数组。如果要返回重复的数组也很简单,我在代码中记录maxLen的地方同时也记录了得到maxLen的maxEnd,这个变量是用来记录取到最长子数组的最后一个位置,由于存的是i,因此是指在A中最后的位置,如果是j则对应为B。因此最终返回的子数组就是A中i的位置往前长度为manLen的子数组。
3. 代码简化
以上的代码是按照dp的过程写的,实际上初始化和递推在这一题中是可以合并到一个循环里写的。
代码如下:
class Solution {
public:
int findLength(vector<int>& A, vector<int>& B) {
int lenA = A.size();
int lenB = B.size();
if(lenA == 0 || lenB == 0)
return 0;
//申请dp二维数组
vector<vector<int>> dp(lenA, vector<int> (lenB, 0));
int maxLen = 0;
int maxEnd = 0;
//初始化边界条件
//递推
for(int i = 0; i < lenA; i++){
for(int j = 0; j < lenB; j++){
if(A[i] == B[j]){
if(i == 0 || j == 0)
dp[i][j] = 1;
else{
dp[i][j] = 1 + dp[i - 1][j - 1];
if(dp[i][j] > maxLen){
maxLen = dp[i][j];
maxEnd = i;
}
}
}
else{
dp[i][j] = 0;
}
}
}
return maxLen;
}
};