给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
示例:
输入:
A: [1,2,3,2,1]
B: [3,2,1,4,7]
输出:3
解释:
长度最长的公共子数组是 [3, 2, 1] 。
提示:
1 <= len(A), len(B) <= 1000
0 <= A[i], B[i] < 100
自我解答(动态规划)
这题太难了,看了提示后也想了好久才想出来。
尝试 “最大子序列和” 的思路,将最优问题转化成
在A列表中,求解出所有以 i 开头/结尾的最大重复子数组长度
记为 f(i)
, 然后尝试寻找 f(i) 与 f(i+1) 的关系,可这样是没有意义的,因为这个列表每个位置 i,
可以和另一个列表的任何元素进行匹配,这就意味着 f(i) 与 f(i+1) 没有关系,仅仅考虑单个列表是不够的。
由此可见,似乎不可避免地需要考虑所有组合数,可是这样的话,就不可避免造成时间复杂度 O(n*m),
这样真的是可接受的吗?为此,我想了很久其他的 O(n) 方案,没有结果。
如果真的需要考虑所有组合数,那又怎么找最优子结构
呢?怎么去勾画最优子问题呢?
设 f(i, j): 以 A[i], B[j] 开头的最长重复子数组长度
发现 最优子结构:
if A[i] == B[j]:
f(i, j) = 1 + f(i+1, j+1)
else:
f(i, j) = 0
因此得出方案一:
1、设计出递归函数 f(i, j)
2、为了覆盖到所有情况,使用两层循环遍历所有的(i, j)组合
3、由于递归函数的存在,循环遍历过程中会出现计算过的f(i, j)组合,因此加入备忘机制
由此可设计出 自顶向下带备忘的 动态规划算法。可惜超时了。
class Solution:
def findLength(self, A: List[int], B: List[int]) -> int:
self.A, self.B = A, B
self.lena, self.lenb = len(A), len(B)
self.cache = {}
self.maxres = 0
for j in range(0, self.lenb):
self.find_len(0, j)
for i in range(1, self.lena):
self.find_len(i, 0)
return self.maxres
def find_len(self, i, j):
if i == self.lena or j == self.lenb:
return 0
if (i, j) in self.cache:
return self.cache[(i, j)]
res = 0
next_len = self.find_len(i + 1, j + 1)
if self.A[i] == self.B[j]:
res = 1 + next_len
if res > self.maxres:
self.maxres = res
self.cache[(i, j)] = res
return res
由于此题的本身就比较复杂,“自顶向下带备忘法” 在此处时间开销过大了(递归栈维护,重复遍历,缓存维护)。
我以为最优子结构找错了,为此又陷入了久久的沉思....
最终发现力扣报超时,是因为超过了python的最大递归深度,看来改成 “自底向上法”
就可以了。
自底向上法是指:将所有子问题按规模从小到大排列,从最小子问题开始,确保在求解每个子问题时,所依赖的前置
子问题都已求解,并且确保每个子问题都只求解一次,不需要缓存备忘。
同样的,最优子问题刻画为:
设 f(i, j): 以 A[i], B[j] 开头的最长重复子数组长度
此题 f(i, j) 的所有组合数就是两个列表两两组合数,其实就是个矩阵, 我们要做的事:
1、矩阵中所有元素只访问一次,即在访问过程中求解 f(i, j)
2、将 f(i, j) 按规模从小到大排列,确保在求解 f(i, j)时, f(i+1, j+1) 已求解
因此, 假设两个列表的长度分别为 N 和 M,可以从 f(N-1, M-1)开始求解, 求解过程:(1 表示已求解)
0, 0, 0, 0 0, 0, 0, 0 0, 1, 0, 0
0, 0, 0, 0 -> 0, 0, 1, 0 -> 0, 0, 1, 0
0, 0, 0, 1 0, 0, 0, 1 0, 0, 0, 1
由此,得到了 方案二:
完成上半部分后,再完成下半部分就可以了
执行结果:
执行用时:3288 ms, 在所有 Python3 提交中击败了 85.24% 的用户
内存消耗:13.5 MB, 在所有 Python3 提交中击败了 89.87% 的用户
class Solution2:
def findLength(self, A: List[int], B: List[int]) -> int:
self.A, self.B, lena, lenb = A, B, len(A), len(B)
self.maxlength = 0
for i in range(lena - 1, -1, -1):
self.get_length(i, lenb - 1)
for j in range(lenb - 2, -1, -1):
self.get_length(lena - 1, j)
return self.maxlength
def get_length(self, i: int, j: int) -> None:
dp = 0
while i >= 0 and j >= 0:
if self.A[i] == self.B[j]:
dp += 1
if dp > self.maxlength:
self.maxlength = dp
else:
dp = 0
i -= 1
j -= 1
原题链接:https://leetcode-cn.com/problems/maximum-length-of-repeated-subarray