最长公共子数组问题
字数 1971 2025-12-15 11:18:45
最长公共子数组问题
题目描述
给定两个整数数组 nums1 和 nums2,返回两个数组中公共的、长度最长的连续子数组的长度。例如:
nums1 = [1,2,3,2,1],nums2 = [3,2,1,4,7],最长公共连续子数组是 [3,2,1],长度为 3。
解题过程循序渐进讲解
第一步:理解问题核心
- “公共连续子数组”要求子数组在两个原数组中连续且顺序一致。
- 与“最长公共子序列(LCS)”不同,LCS 允许不连续,而本题要求连续。
- 目标:找出最长公共连续子数组的长度。
第二步:暴力解法(思考起点)
- 最直接的方法:枚举
nums1的所有连续子数组,在nums2中查找匹配的连续子数组。 - 步骤:
- 遍历
nums1的所有起点i和终点j(i ≤ j),得到子数组nums1[i:j+1]。 - 在
nums2中遍历所有起点p,检查nums2[p:p+len]是否与nums1[i:j+1]完全相同(len = j-i+1)。
- 遍历
- 时间复杂度:O(m² * n * min(m,n)),其中 m、n 为数组长度。效率极低。
第三步:动态规划思路引入
- 观察到:如果
nums1[i] == nums2[j],且我们知道以nums1[i-1]和nums2[j-1]结尾的公共子数组长度,就能递推。 - 定义状态:
dp[i][j]表示以nums1[i-1]和nums2[j-1]结尾的公共连续子数组的长度。- 使用
i-1、j-1是为了方便处理边界(避免 i=0 或 j=0 时的额外判断)。
- 使用
- 状态转移方程:
如果nums1[i-1] == nums2[j-1],则dp[i][j] = dp[i-1][j-1] + 1。
否则dp[i][j] = 0(因为连续要求,一旦不等,当前结尾的公共长度就为 0)。 - 最终答案:所有
dp[i][j]中的最大值。
第四步:动态规划实现细节
- 初始化二维数组
dp,大小为(m+1) × (n+1),初始值全为 0。 - 两层循环遍历
i从 1 到 m,j从 1 到 n:- 比较
nums1[i-1]和nums2[j-1]。 - 若相等,
dp[i][j] = dp[i-1][j-1] + 1。 - 同时更新全局最大值
max_len。
- 比较
- 返回
max_len。
示例推导(用题目例子)
nums1 = [1,2,3,2,1]
nums2 = [3,2,1,4,7]
dp 表(只写部分关键值):
- i=3(nums1[2]=3), j=1(nums2[0]=3):相等 → dp[3][1] = dp[2][0]+1 = 1
- i=4(2), j=2(2):相等 → dp[4][2] = dp[3][1]+1 = 2
- i=5(1), j=3(1):相等 → dp[5][3] = dp[4][2]+1 = 3
最大值为 3。
第五步:优化空间复杂度
- 观察状态转移:
dp[i][j]只依赖于dp[i-1][j-1],即左上角的值。 - 可以优化为一维数组
dp,长度n+1。 - 遍历顺序:
i从 1 到 m,j从 n 到 1(倒序,避免覆盖上一行需要的dp[j-1])。 - 状态转移:
如果nums1[i-1] == nums2[j-1],则dp[j] = dp[j-1] + 1;否则dp[j] = 0。
注意:dp[j-1]是上一轮(i-1)时j-1的值,即原来的dp[i-1][j-1]。 - 空间复杂度从 O(m×n) 降为 O(n)。
第六步:代码实现(Python)
def findLength(nums1, nums2):
m, n = len(nums1), len(nums2)
dp = [0] * (n + 1)
max_len = 0
for i in range(1, m + 1):
# 倒序更新,防止覆盖
for j in range(n, 0, -1):
if nums1[i-1] == nums2[j-1]:
dp[j] = dp[j-1] + 1
max_len = max(max_len, dp[j])
else:
dp[j] = 0
return max_len
第七步:复杂度分析
- 时间复杂度:O(m × n),两层循环。
- 空间复杂度:O(n),使用一维 dp 数组。
第八步:扩展思考
- 如果要求输出最长公共子数组本身(而不仅是长度),可以在更新
max_len时记录对应的索引位置。 - 另一种解法:滑动窗口法(对齐两个数组的起始位置,模拟错位比较),时间复杂度相同,但常数可能更优。
- 相关题目对比:
- 最长公共子序列(LCS):允许不连续,dp 状态转移为
dp[i][j] = max(dp[i-1][j], dp[i][j-1])(当字符不等时)。 - 最长重复子数组:实际是本题的特例(两个输入数组相同)。
- 最长公共子序列(LCS):允许不连续,dp 状态转移为
总结
最长公共子数组问题是动态规划的经典应用,通过定义“以当前位置结尾”的状态,将连续匹配的信息传递下去。掌握一维空间优化技巧,可以提升实际面试中的代码效率。