最长重复子数组的变种:最多允许k次修改的最长公共子数组
题目描述
给定两个整数数组nums1和nums2,以及一个整数k。要求找到最长的公共子数组(连续),且允许在任意一个数组中最多次修改k个元素的值(修改后元素可以变成任意值)。返回最长公共子数组的长度。
示例:
输入:nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7], k = 1
输出:3
解释:最长公共子数组是[3,2,1],在nums2中只需修改1个元素(将4改为1)即可匹配。
解题过程
1. 问题分析
这是最长公共子数组问题(最长公共连续子序列)的变种,增加了"最多允许k次修改"的条件。我们需要找到两个数组中最长的连续公共部分,但允许最多k个位置不匹配(通过修改来匹配)。
关键点:
- 子数组必须是连续的
- 允许最多k次修改(在两个数组中的任意位置)
- 修改可以将元素改为任意值
2. 基本思路
使用动态规划,但需要记录匹配状态和已使用的修改次数。定义dp[i][j][m]表示以nums1的第i个元素和nums2的第j个元素结尾,使用了m次修改的最长公共子数组长度。
3. 状态转移方程
对于每个位置(i, j)和修改次数m:
- 如果nums1[i] == nums2[j]:
- 不需要使用修改,继承前一个状态
- dp[i][j][m] = dp[i-1][j-1][m] + 1
- 如果nums1[i] != nums2[j]:
- 如果还有修改次数可用(m > 0):
- 使用一次修改来匹配
- dp[i][j][m] = dp[i-1][j-1][m-1] + 1
- 如果没有修改次数可用:
- dp[i][j][m] = 0(无法匹配)
- 如果还有修改次数可用(m > 0):
边界条件:
- 当i=0或j=0时,dp[i][j][m] = 1(如果nums1[i] == nums2[j])或0(如果不相等且m=0)
4. 优化空间复杂度
由于dp[i][j][m]只依赖于dp[i-1][j-1][m]和dp[i-1][j-1][m-1],我们可以使用滚动数组优化,将三维dp降为二维。
定义dp[j][m]表示当前行以nums2的第j个元素结尾,使用了m次修改的最长公共子数组长度。
5. 具体实现步骤
def findLength(nums1, nums2, k):
m, n = len(nums1), len(nums2)
# 初始化dp数组
dp = [[0] * (k + 1) for _ in range(n + 1)]
max_len = 0
for i in range(1, m + 1):
# 保存上一行的状态
prev = [row[:] for row in dp]
for j in range(1, n + 1):
for mod in range(k + 1):
if nums1[i-1] == nums2[j-1]:
# 元素相等,直接继承前一个状态
dp[j][mod] = prev[j-1][mod] + 1
elif mod > 0:
# 元素不等但还有修改次数
dp[j][mod] = prev[j-1][mod-1] + 1
else:
# 元素不等且没有修改次数
dp[j][mod] = 0
# 更新最大值
max_len = max(max_len, dp[j][mod])
return max_len
6. 时间复杂度分析
- 时间复杂度:O(m × n × k),其中m和n分别是数组长度,k是允许的修改次数
- 空间复杂度:O(n × k),通过滚动数组优化
7. 示例验证
以nums1 = [1,2,3,2,1], nums2 = [3,2,1,4,7], k = 1为例:
计算过程:
- 当i=3, j=1时(对应元素3和3):匹配,长度=1
- 当i=4, j=2时(对应元素2和2):匹配,长度=2
- 当i=5, j=3时(对应元素1和1):匹配,长度=3
- 最终找到最长公共子数组长度为3
8. 进一步优化思路
对于更大的k值,可以考虑使用滑动窗口+二分查找的方法,将时间复杂度优化到O((m+n)×log(min(m,n))),但这会牺牲一些准确性。