最⻓重复⼦数组
力扣:718. 最长重复子数组 - 力扣(LeetCode)
给两个整数数组 nums1
和 nums2
,返回两个数组中 公共的 、长度最长的子数组的长度 。
示例 1:
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7]
输出:3
解释:长度最长的公共子数组是 [3,2,1] 。
示例 2:
输入:nums1 = [0,0,0,0,0], nums2 = [0,0,0,0,0]
输出:5
提示:
- 1 <= nums1.length, nums2.length <= 1000
- 0 <= nums1[i], nums2[i] <= 100
注意题⽬中说的⼦数组,其实就是连续⼦序列。子序列默认不连续,子数组默认连续。这种问题动规虽然不是最优解但是最用动规解答最简单。
动规五部曲:
1. 确定dp数组以及下标的含义
i = nums1.length + 1; j = nums2.length + 1
dp[i][j]:以下标i - 1为结尾的nums1,和以下标j- 1为结尾的nums2,最⻓重复⼦数组⻓度为dp[i][j]。
此时细⼼的朋友应该发现,那dp[0][0]是什么含义呢?总不能是以下标-1为结尾的A数组吧。其实dp[i][j]的定义也就决定着,我们在遍历dp[i][j]的时候i 和 j都要从1开始。
那定义dp[i][j]为 以下标i为结尾的A,和以下标j 为结尾的B,最⻓重复⼦数组⻓度。不⾏么?
⾏倒是⾏! 但实现起来就麻烦⼀点,⼤家看下⾯的dp数组状态图就明⽩了。
2. 确定递推公式
根据dp[i][j]的定义,dp[i][j]的状态只能由dp[i - 1][j - 1]推导出来。
即当A[i - 1] 和B[j - 1]相等的时候,dp[i][j] = dp[i - 1][j - 1] + 1;
根据递推公式可以看出,遍历i 和 j 要从1开始!
3. dp数组如何初始化
根据dp[i][j]的定义,dp[i][0] 和dp[0][j]其实都是没有意义的!
但dp[i][0] 和dp[0][j]要初始值,因为 为了⽅便递归公式dp[i][j] = dp[i - 1][j - 1] + 1;
所以dp[i][0] 和dp[0][j]初始化为0。举个例⼦A[0]如果和B[0]相同的话,dp[1][1] = dp[0][0] + 1,只有dp[0][0]初始为0,正好符合递推公式逐步累加起来。
4. 确定遍历顺序
外层for循环遍历nums,内层for循环遍历nums2。
当然外层for循环遍历nums2,内层for循环遍历nums。也⾏,⼀样的。
我这⾥就⽤外层for循环遍历A,内层for循环遍历B了。
同时题⽬要求⻓度最⻓的⼦数组的⻓度。所以在遍历的时候顺便把dp[i][j]的最⼤值记录下来。
Java代码如下:
for(int i = 1; i <= l; i++)
for(int j = 1; j <= l2; j++){
if(nums1[i - 1] == nums2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
max = Math.max(max, dp[i][j]);
}
5. 举例推导dp数组
分析完毕Java代码如下:
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int l = nums1.length;
int l2 = nums2.length;
int[][] dp = new int[l + 1][l2 + 1];
int max = 0;
for(int i = 1; i <= l; i++)
for(int j = 1; j <= l2; j++){
if(nums1[i - 1] == nums2[j - 1])
dp[i][j] = dp[i - 1][j - 1] + 1;
max = Math.max(max, dp[i][j]);
}
return max;
}
}