Given two integer arrays A
and B
, return the maximum length of an subarray that appears in both arrays.
Example 1:
Input: A: [1,2,3,2,1] B: [3,2,1,4,7] Output: 3 Explanation: The repeated subarray with maximum length is [3, 2, 1].
Note:
- 1 <= len(A), len(B) <= 1000
- 0 <= A[i], B[i] < 100
题意:
输入两个数组,它们的最长共同子数组的长度是多少?例如输入数组{1, 2, 3, 2, 1} 和 {3, 2, 1, 4, 7},它们的最长共同子数组是{3, 2, 1},长度为3。
分析:
根据题意应该可以分析出这是一道动态规划题。
应用动态规划的第一步是自上而下分析即找出递归函数。
我们定义一个函数f(i,j)表示第一个数组中以第i个数字结尾的子数组和第二个数组中以j个数字为结尾的子数组的最长共同子数组的长度。这个问题的解就是所有f(i,j)的最大值。
当第一个数组中的第i个数字和第二个数组的第j个数字不相同,则f(i,j)等于0,因为以这两个数字结尾的所有子数组都不可能是两个数组的共同子数组。
当第一个数组中的第i个数字和第二个数组的第j个数字相同时,f(i,j)等于f(i-1,j-1)+1,这相当于在第一个数组中的第i-1个数字和第二个数组的第j-1个字符后面添加了一个共同的数字。
应用动态规划的第二步是自下而上求解。为了避免重复求解子问题,动态规划通常是从最小的子问题开始求解,并把子问题的解保存下来,以后再需要用到这些子问题的解时就不用重复计算了。
我们可以把所有f(i,j)的值保存到如下的一个二维数组里。数组的第i行第j列表示f(i,j)的值。例如第一个数组中下标为1的数字和第二个数组中下标为1的数字相同,都是2,但它们在两个数组中的前一个数字不同,因此以它们结尾的最长共同子数组只能包含一个数字2,长度为1。因此下图二维数组中坐标为(1,1)的值为1。
第一个数组中下标为3的数字和第二个数组中下标为1的数字相同,都是2。同时它们在两个数组中前一个数字也相同,都是3。这时以这两个2结尾的最长共同子数组是{3,2},长度为2。因此下图二维数组中坐标为(3,1)的值为1。
看起来我们需要一个二维数组才能求出所有的f(i,j),实际上我们只需要保存二维数组中的一行就够了。这是因为我们在求解f(i,j)时,我们只需要用到第i-1行的值,再之前的值我们没有比较一直保存着。
下面的Java代码实现了上述思路:
public int findLength(int[] A, int[] B) {
if (null == A || null == B) {
return 0;
}
int lengthA = A.length;
int lengthB = B.length;
int[] length = new int[lengthB];
int max = 0;
for (int i = 0; i < lengthA; i ++) {
for (int j = lengthB-1; j >= 0; j --) {
if (A[i] != B[j]) {
length[j] = 0;
} else if (i == 0 || j == 0) {
length[j] = 1;
} else {
length[j] = length[j-1] + 1;
max = Math.max(max, length[j]);
}
}
}
return max;
}
上述代码中有一个细节值得大家注意:在第二个for循环中j的值是从大到小变化,相当于从右向左扫描一遍数组lens。这是因为求上图二维数组中坐标(i,j)的值时,有可能用到坐标为(i-1,j-1)的值即lens[j-1]。计算得到二维数组中坐标(i,j)的值之后存入到数组lens[i]之中。相当于从右向左扫描一遍数组lens就可以把二维数组中第i-1行的值逐个替换成第i行的值。
如果两个数组的长度分别为m和n,这种解法的时间复杂度是O(mn),同时需要O(n)的辅助空间。如果面试官要求进一步优化空间效率,我们还有两个选择:
- 在创建数组lens之前比较两个数组的长度。数组lens的长度可以是较短数组的长度;
- 通常情况下数组lens中的大部分值应该是0。我们可以不用数组而用哈希表只保存非0的值。该哈希表的键值相当于数组的下标,哈希表的值相当于数组的值。
这道题还可能会有一下两种变化:
(1)求两个字符串中最长共同子字符串的长度。这两个问题实际上完成一样的,只是换一个说法而已。
(2)求两个数组中最长共同子序列的长度。子数组一定是连续的,而子序列则不一定。当第一个数组的第i个数字等于第二个数组的第j个数字时,递归函数f(i,j)等于f(i-1,j-1)+1;否则f(i,j)等于f(i-1,j)和f(i,j-1)的较大值。感兴趣的不妨仔细自上而下分析得出此递归函数,并写代码解决这个问题。