最长重复子数组 图解题解
这道题到底在问什么
- 输入
- A=[1,2,3,2,1], B=[3,2,1,4,7]
- 输出
- 3
最优解:为什么这么做
一句话答案:LeetCode 718 最长重复子数组求两数组最长公共连续子数组:dp[i][j] 记以 A[i-1]、B[j-1] 结尾的公共长度,相等接左上加一、不等归零,答案取全表最大。时间 O(m·n)、空间可滚动到 O(n)。
两数组的最长公共子数组,连续这两个字卡在哪
给两个整数数组 A 和 B,要在它们里各挑出一段完全相同、而且在各自数组里连着排的片段,返回这段能有多长。题面 A=[1,2,3,2,1]、B=[3,2,1,4,7],两边都出现过又都连续的最长片段是 [3,2,1],长度 3。难点全压在「连续」上:中间断了一格,这段就不算数,得从断点重新数起。
把每对起点都往后比一遍为什么撑不住
一个直觉做法是把 A 的每个起点、B 的每个起点两两配对,再沿着往后逐位比对能对齐多长。A、B 各有 m、n 个起点,每对还要往后扫一趟,三层嵌套,长度一大就跑不完。相邻起点比出的前半截还大量重叠、被反复重算,把「以某两个位置结尾能对齐多长」记下来复用就能省掉。
dp[i][j] 为什么非得钉在以这两个数结尾
定义 dp[i][j] 为:A 的前 i 个数、B 的前 j 个数里,分别以 A[i-1] 和 B[j-1] 结尾的公共连续片段有多长。把结尾钉死这一步不能省——只有规定了以这两个数收尾,连续才有着落;要是定成「任意位置的最长公共片段」,中间断没断就说不清,连续这条约束会漏掉。为了下标不越界,dp 开成 (m+1)×(n+1),第 0 行第 0 列全填 0,表示有一边没有数时公共长度只能是 0。
相等接左上加一、不等直接归零,凭什么
填 dp[i][j] 只看 A[i-1] 和 B[j-1] 这一对。它俩相等,就把这对接到上一对结尾的片段后面、长度加一,即 dp[i][j] = dp[i-1][j-1] + 1,左上那格记的正是去掉这对之后还连着的那截有多长。它俩不等,以这对结尾就连不起来,直接写 0,从这里重新数起。和最长公共子序列分道,也在这一步:子序列可以跳着挑,不等时取上、左较大的接着攒;子数组必须连续,不等就归零,一点不留。填表时随手用一个变量记下见过的最大 dp 值,就是答案。
拿 [1,2,3,2,1] 和 [3,2,1,4,7] 手填一遍
行对 A、列对 B,第 0 行第 0 列全是 0。第一行 A=1:只有对上 B 的 1 那格 dp[1][3]=dp[0][2]+1=1,其余不等全 0。第二行 A=2:对上 B 的 2,dp[2][2]=dp[1][1]+1=0+1=1。第三行 A=3:对上 B 的 3,dp[3][1]=dp[2][0]+1=1。第四行 A=2:对上 B 的 2,dp[4][2]=dp[3][1]+1=1+1=2,这里接住了上一行的 1。第五行 A=1:对上 B 的 1,dp[5][3]=dp[4][2]+1=2+1=3。全表最大值是 dp[5][3]=3,对应的正是 [3,2,1],和答案对上。
复杂度落在哪,答案为什么不在右下角
表有 m×n 格,每格只做一次比较和一次加法,时间 O(m·n);整表要 O(m·n) 空间,但每格只用到左上一格,倒着遍历列就能压成一维,空间降到 O(n)。别拿右下角 dp[m][n] 当答案——最长片段可能收在表里任何位置,得边填边记全局最大。不等时也别顺手写『取上、左较大』,那是子序列的转移,会把断开的片段续上;子数组这里只能归零。
▶ 动画逐步走查(共 53 步)——想跟着动画一帧帧对照就展开
- 3关键:dp[i][j] 必须以这两个元素结尾。这样「连续」才有保证——一旦不等就归零,重新开始数。
- 4左上一圈先铺好 0:只要有一边没有元素,公共子数组长度就是 0。
- 5A 的 1 和 B 的 3 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 6落子:dp[1][1] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 7A 的 1 和 B 的 2 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 8落子:dp[1][2] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 9A 的 1 和 B 的 1 相等:在左上角「上一对结尾」的 0 基础上,把这一对接上去,长度 +1。
- 10落子:dp[1][3] = 1。每接上一对相等的数,连续公共子数组就长 1,同时刷新全局最大值。
- 11A 的 1 和 B 的 4 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 12落子:dp[1][4] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 13A 的 1 和 B 的 7 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 14落子:dp[1][5] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 15A 的 2 和 B 的 3 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 16落子:dp[2][1] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 17A 的 2 和 B 的 2 相等:在左上角「上一对结尾」的 0 基础上,把这一对接上去,长度 +1。
- 18落子:dp[2][2] = 1。每接上一对相等的数,连续公共子数组就长 1,同时刷新全局最大值。
- 19A 的 2 和 B 的 1 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 20落子:dp[2][3] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 21A 的 2 和 B 的 4 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 22落子:dp[2][4] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 23A 的 2 和 B 的 7 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 24落子:dp[2][5] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 25A 的 3 和 B 的 3 相等:在左上角「上一对结尾」的 0 基础上,把这一对接上去,长度 +1。
- 26落子:dp[3][1] = 1。每接上一对相等的数,连续公共子数组就长 1,同时刷新全局最大值。
- 27A 的 3 和 B 的 2 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 28落子:dp[3][2] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 29A 的 3 和 B 的 1 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 30落子:dp[3][3] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 31A 的 3 和 B 的 4 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 32落子:dp[3][4] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 33A 的 3 和 B 的 7 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 34落子:dp[3][5] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 35A 的 2 和 B 的 3 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 36落子:dp[4][1] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 37A 的 2 和 B 的 2 相等:在左上角「上一对结尾」的 1 基础上,把这一对接上去,长度 +1。
- 38落子:dp[4][2] = 2。每接上一对相等的数,连续公共子数组就长 1,同时刷新全局最大值。
- 39A 的 2 和 B 的 1 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 40落子:dp[4][3] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 41A 的 2 和 B 的 4 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 42落子:dp[4][4] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 43A 的 2 和 B 的 7 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 44落子:dp[4][5] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 45A 的 1 和 B 的 3 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 46落子:dp[5][1] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 47A 的 1 和 B 的 2 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 48落子:dp[5][2] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 49A 的 1 和 B 的 1 相等:在左上角「上一对结尾」的 2 基础上,把这一对接上去,长度 +1。
- 50落子:dp[5][3] = 3。每接上一对相等的数,连续公共子数组就长 1,同时刷新全局最大值。
- 51A 的 1 和 B 的 4 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 52落子:dp[5][4] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 53A 的 1 和 B 的 7 不相等:连续被打断了,以这两个数结尾的公共子数组长度只能是 0。
- 54落子:dp[5][5] = 0。和子序列不同——这里不取上/左较大,直接清零,因为子数组必须连续。
- 55答案不是右下角,而是整张表里的最大值:dp[5][3] = 3,对应公共子数组 [3,2,1]。
⚠️ 容易写错的地方
✗ 错:取右下角当答案
✓ 对:答案是「全表最大值」
最长重复子数组可能结束在任意位置,不一定在末尾
✗ 错:不等时取上/左较大
✓ 对:不等直接清零
子数组必须连续,断了就不能继续累加
✗ 错:当成最长公共子序列
✓ 对:子数组要连续、子序列可跳
子序列不等时取 max,子数组不等时归零
完整代码(Python / C++ / Java)
Python
def findLength(A, B):
m, n = len(A), len(B)
dp = [[0]*(n+1) for _ in range(m+1)]
ans = 0
for i in range(1, m+1):
for j in range(1, n+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
ans = max(ans, dp[i][j])
return ansC++
int findLength(vector<int>& A, vector<int>& B){
int m = A.size(), n = B.size(), ans = 0;
vector<vector<int>> dp(m+1, vector<int>(n+1, 0));
for(int i = 1; i <= m; i++)
for(int j = 1; j <= n; j++)
if(A[i-1] == B[j-1]){
dp[i][j] = dp[i-1][j-1] + 1;
ans = max(ans, dp[i][j]);
}
return ans;
}Java
int findLength(int[] A, int[] B){
int m = A.length, n = B.length, ans = 0;
int[][] dp = new int[m + 1][n + 1];
for (int i = 1; i <= m; i++)
for (int j = 1; j <= n; j++)
if (A[i-1] == B[j-1]) {
dp[i][j] = dp[i-1][j-1] + 1;
ans = Math.max(ans, dp[i][j]);
}
return ans;
}复杂度
时间
O(m·n)
每格 O(1),共 m×n 格
空间
O(m·n)
整表;因只依赖上一行,可滚动到 O(n)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最长重复子数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么答案是全表最大值而不是 dp[m][n]?+
dp[i][j] 记的是「以 A[i-1]、B[j-1] 结尾」的公共连续片段长度,而最长的那段可能结束在两数组的任意位置,不一定卡在末尾。所以要在填表过程中用一个变量随手记下见过的最大 dp 值,最后返回它;只读右下角 dp[m][n],会漏掉结束在中间的更长片段。
和最长公共子序列(LeetCode 1143)到底差在哪?+
两题都是二维 dp、都比 A[i-1] 和 B[j-1],差别全在不等时怎么办。子序列允许跳着挑,不等时取 dp[i-1][j] 和 dp[i][j-1] 里较大的接着攒,答案落在右下角;子数组要求连续,不等时 dp[i][j] 直接归零、从头再数,答案在全表取最大。一个断了能续、一个断了清零,这是两题的分水岭。
空间能优化吗?+
能。dp[i][j] 只依赖左上方的 dp[i-1][j-1],把二维压成一维 dp[j] 即可,但内层要倒着遍历 j(从大到小),否则 dp[j-1] 会被本行新算的值覆盖、读不到上一行的旧值。这样空间从 O(m·n) 降到 O(n)。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最长重复子数组 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。