最长等差数列 图解题解
这道题到底在问什么
- nums
- [9,4,7,2,10]
- 输出
- 3
- 解释
- 选 4、7、10(公差 +3),长度 3,是最长的
先想最直接的笨办法
如果公差给定,这题就退化成「固定差值的最长等差子序列」——仍是子序列、可以跳着挑,只少枚举公差这一维,用值到长度的哈希表扫一遍即可。麻烦在于公差没给、可正可负、范围很大,得想个办法让每种公差各自记一笔账。(动画第 3 步)
最优解:为什么这么做
一句话答案:LeetCode 1027 最长等差数列:公差事先不给,让每个下标各带一张「公差→长度」哈希表,dp[i][d]=dp[j][d]+1(d=nums[i]−nums[j]),找不到按 1 起算。时间与空间都是 O(n²)。
最长等差数列这道题到底要挑出什么
给一个整数数组 nums,挑出最长的等差子序列、返回长度。子序列(跳着挑但保持原顺序的一串)不必连续,等差要求相邻两项之差处处相等,这个差叫公差。题面 nums=[9,4,7,2,10] 答案 3:取出 4、7、10(隔着 9 和 2),两两差 +3。
公差没给,为什么挨个去试会又慢又乱
公差可正可负、值域一大就近乎无穷种,先枚举公差再回数组凑链,得把所有差各扫一趟,还大量重复重算。固定「以某个数结尾」,只问前面哪个数能接上来,把算好的链长记下复用,就是动态规划(把『以某数结尾、某公差的最长链』算一次记下、后面直接取),指数级乱试便压成两层循环的递推。
公差进不了普通下标,怎么让它当状态(要盯住哪几个量)的一部分
假如公差给定,这题就退化成「固定差值的最长子序列」,一趟扫描能解;它没给。于是把公差塞进状态:定义 dp[i][d](以下标 i 结尾、公差为 d 的最长等差子序列有多长)。公差有无穷多种、整数下标装不下,就用哈希表(按键存值、查改都快的表)存,公差当键、链长当值。每个下标 i 各挂一张小表,只为出现过的公差开格子。
接到前面同公差的链上,长度为什么正好加 1
算 dp[i][d] 时定 nums[i] 当结尾,看前面某个 nums[j],两者之差 d=nums[i]−nums[j] 就是公差。把 nums[i] 接到「以 nums[j] 结尾、同差 d」的最长链尾上,长度加 1,即 dp[i][d]=dp[j][d]+1。若下标 j 的账里没有 d,nums[j] 就是这条 d 链的开头(长度 1),接上得 2,参考代码 dp[j].get(d, 1) 的默认值 1 正好收了这情况。公差固定后,只要 nums[j] 那条同差链最长,接上仍最长。
拿 [9,4,7,2,10] 亲手把这几本小账填一遍
排开 nums=[9,4,7,2,10](下标 0 到 4),各备空账、ans 从 1 起。下标 1 的 4 回看 9:公差 −5,dp[1][−5]=2。下标 2 的 7:配 9 得 −2、配 4 得 +3,各长度 2,账里从此有「+3 → 2」。下标 3 的 2 配出的三个差全是新差、接不长。下标 4 的 10:配 9 得 +1、配 4 得 +6 都只 2;配 7 公差 +3,回头查下标 2 正存着「+3 → 2」,dp[4][+3]=2+1=3。各下标最长 1、2、2、2、3,全局取 3,对应 4→7→10,和答案 3 对上。
找不到公差就按 0 起算,答案为什么会整整缩水 1
把 get 的默认值写成 0,dp[1][−5] 就成了 0+1=1,往后每条链都比真相少 1 节,示例从 3 缩成 2。根子在默认值的含义:nums[j] 自己就是长度 1 的起点,找不到同差前驱时该按 1 起算,而非 0。
复杂度上,两层循环共 n²/2 对、每对一次哈希查改,时间 O(n²)(大 O 记号,描述规模变大时操作数怎么涨);每个下标存前面那些公差,空间也是 O(n²)。两处别弄错:公差 0 也合法,三个相同的 7 是长度 3 的等差数列;ans 初值给 1,单个数就是长度 1 的等差数列。
▶ 动画逐步走查(共 26 步)——想跟着动画一帧帧对照就展开
- 3如果公差给定,这题就退化成「固定差值的最长等差子序列」——仍是子序列、可以跳着挑,只少枚举公差这一维,用值到长度的哈希表扫一遍即可。麻烦在于公差没给、可正可负、范围很大,得想个办法让每种公差各自记一笔账。
- 4暴力枚举公差再逐个匹配,慢且乱。换个视角:固定以某个数结尾,再问「它前面哪个数能接上来」,这就有了 DP 的味道。
- 5对每个 i,枚举它前面的 j:公差 d = nums[i] − nums[j]。那么把 nums[i] 接到「以 nums[j] 结尾、同样公差 d」的串后面,长度 +1。公差用哈希表当下标,多少种差都装得下。
- 6若 j 那里本来没有公差 d 的记录,说明 nums[j] 是这串的开头,按长度 1 起算,接上 nums[i] 就是 2。下面用示例 [9,4,7,2,10] 一对一对地填这张表。
- 7下标 0..4下面这排是原数组,格子下方的小数字是下标 i。算法外层循环让 i 从 1 走到 4,内层让 j 扫遍 i 左边所有位置,逐对算公差。
- 8答案藏在 4、7、10提前把答案点出来:绿色的三个数 4、7、10(下标 1、2、4)隔着 9 和 2 取出来,两两差 +3。接下来看 DP 怎么把这条链一格一格拼出来。
- 9i 从下标 1 起步正式开扫。外层 指针 i 落在下标 1。下标 0 前面没有数能配对,所以 i 从 1 起步。每到一个 i,就让内层 j 扫一遍它左边所有位置。
- 10记录每个 i 处「公差→最长长度」这张表的列是结尾下标 i。每个 i 自带一本「公差→长度」的小账(哈希),单个数起步长度都是 1。中间两行会显示当前正在算的那一对的公差和结果,末行汇总每列见过的最长。
- 11d = 4 − 9 = −5第一对:i=1(值4)接 j=0(值9)。公差 −5。下标 0 那本账里没有 −5 的记录,说明 9 是开头(长度1),接上 4 得 2。i=1 处最长更新成 2。
- 12i 右移到下标 2(值7)外层 i 推进到下标 2(值7)。它左边有两个位置 0、1,内层 j 会挨个去配,各算一个公差。先看 j=0。
- 13d = 7 − 9 = −2轮到 i=2(值7),先配最左的 j=0(值9)。公差 −2,下标 0 没这个差,得长度 2(9→7)。i=2 处最长记为 2。
- 14d = 7 − 4 = +3再配 j=1(值4)。公差 +3!这正是答案那条链的公差。下标 1 那本账里还没有 +3,所以 4 是这条 +3 链的开头,接上 7 得长度 2(4→7)。记牢 i=2 处有一笔「+3 → 2」,下面要用到它。
- 15i 右移到下标 3(值2)i 推进到下标 3(值2)。它左边有三个位置,j 会依次配 0、1、2。盯着看值 2 能不能接上某条已有的链。
- 16d = 2 − 9 = −7i=3(值2)登场,先配 j=0(值9)。公差 −7,新差,长度 2(9→2)。i=3 处最长记 2。
- 17d = 2 − 4 = −2再配 j=1(值4),公差 −2。下标 1 没这个差,长度 2(4→2)。i=3 处最长还是 2。
- 18d = 2 − 7 = −5最后配 j=2(值7),公差 −5。i=2 那本账里没有 −5(它只有 −2 和 +3),所以长度 2(7→2)。i=3 全部配完,最长仍 2。值 2 是个「死胡同」,对答案没贡献。
- 19i 右移到末位下标 4(值10)最后一列!i 到末位下标 4(值10)。它左边四个位置都要配。重点盯 j=2(值7) 那一对——前面 i=2 处存的「+3 → 2」马上派上用场。
- 20d = 10 − 9 = +1最后的 i=4(值10)是决胜列。先配 j=0(值9),公差 +1,长度 2(9→10)。i=4 处最长记 2。
- 21d = 10 − 4 = +6再配 j=1(值4),公差 +6,新差,长度 2(4→10)。还没碰到关键的那一对。
- 22d = 10 − 7 = +3 命中已有链关键一击!配 j=2(值7),公差 +3。回头查 i=2 那本账——里头正好存着「+3 → 2」(就是刚才 4→7 那条链)!于是 10 接上去:dp[4][+3] = 2 + 1 = 3。4→7→10 这条链终于拼成长度 3。
- 23d = 10 − 2 = +8最后配 j=3(值2),公差 +8,长度 2。它没能超过刚才的 3,所以 i=4 处最长稳定在 3。所有对都配完了。
- 24答案 = 所有 dp 里的最大值 = 3看末行「i 处最长」:1、2、2、2、3,全局最大是 3,出现在 i=4。它对应的链就是高亮的 4、7、10。和示例答案 3 完全对上。
- 254 → 7 → 10,公差 +3回头看这条绿链:下标 1、2、4 的 4、7、10,跳过 9 和 2,公差恒为 +3。DP 正是靠「每个数继承前面同公差的最长链」把它一节一节接出来的。
- 26两层循环 i、j 共 O(n²) 对,每对只做一次哈希查改 O(1),整体 O(n²)。关键就是「公差当哈希键」,让无穷多种公差各记各的账,互不打架。
- 29公差 d = nums[i]−nums[j],可正可负、范围可能很大(题目值域大时会爆)。哈希表只为真实出现过的公差开格子,省空间又不用做下标偏移。值域确定时也可以用 dp[i][d+offset] 的二维数组替代。
- 31i=2 配 j=1 错算成 1如果 dp[1] 里没有 +3 时按 0 起算,dp[2][+3] 只得 1,等于把开头的 4 当成不存在。这样后面 10 接上来也只到 2,最终答案错成 2。正确是按 1 起算(4 自己长度 1),才得 4→7=2、再到 4→7→10=3。
⚠️ 容易写错的地方
✗ 错:答案初始化成 0
✓ 对:初始化成 1
单个数本身就是长度 1 的等差数列
✗ 错:j 没找到 d 时按 0 接
✓ 对:按 1 接(get(d,1))
nums[j] 是这条链的开头,本身算长度 1
✗ 错:把公差的正负号弄丢/取绝对值
✓ 对:保留带符号的差
+3 链和 −3 链是两条不同的链,不能混
完整代码(Python / C++ / Java)
Python
class Solution:
def longestArithSeqLength(self, nums):
n = len(nums)
dp = [dict() for _ in range(n)] # dp[i][d]=以i结尾、公差d的长度
ans = 1
for i in range(1, n):
for j in range(i):
d = nums[i] - nums[j] # 公差
dp[i][d] = dp[j].get(d, 1) + 1 # 接到j的同差链后
ans = max(ans, dp[i][d])
return ansC++
class Solution {
public:
int longestArithSeqLength(vector<int>& nums) {
int n = nums.size();
vector<unordered_map<int,int>> dp(n); // dp[i][d]
int ans = 1;
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
int d = nums[i] - nums[j]; // 公差
int prev = dp[j].count(d) ? dp[j][d] : 1;
dp[i][d] = prev + 1;
ans = max(ans, dp[i][d]);
}
}
return ans;
}
};Java
class Solution {
public int longestArithSeqLength(int[] nums) {
int n = nums.length;
Map<Integer,Integer>[] dp = new HashMap[n]; // dp[i][d]
int ans = 1;
for (int i = 0; i < n; i++) dp[i] = new HashMap<>();
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
int d = nums[i] - nums[j]; // 公差
int prev = dp[j].getOrDefault(d, 1);
dp[i].put(d, prev + 1);
ans = Math.max(ans, dp[i].get(d));
}
}
return ans;
}
}复杂度
时间复杂度
O(n²)
外层 i、内层 j 共 n²/2 对,每对哈希查改 O(1)
空间复杂度
O(n²)
每个下标一张哈希表,最坏每个 i 存 O(i) 种公差,合计 O(n²)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 最长等差数列 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
1027 和 LeetCode 873 最长斐波那契子序列是姊妹题吗?+
是,套路几乎同源,都靠「双下标定状态 + 哈希查前驱」。873 要求后一项等于前两项之和(斐波那契式),状态用一对下标 (i,j) 定、哈希按数值反查前驱;1027 要求相邻两项差固定,状态用「结尾下标 + 公差」定、哈希按公差记账。两题都跳出了「枚举整条链」,改成「以某处结尾、往前接一步」。会了一题,另一题只需把接续条件从『相加』换成『等差』。
为什么找不到公差时要按 1 起算,而不是 0?+
dp[j].get(d, 1) 里的默认值 1 代表「nums[j] 自己就是一条长度 1 的等差链起点」,任何单个数都是合法的长度 1 等差数列。若默认写 0,等于假装这个开头的数不存在,接上去的链会整整少 1 节,示例 [9,4,7,2,10] 会把答案错算成 2。这是本题最常见的错法。
公差范围很大,用哈希表会不会又慢又费空间?+
哈希表只为真实出现过的公差开格子,n 个数最多产生 O(n²) 种公差,空间随数据自适应,查改仍接近一步。若题目值域已知且不大,也可以把公差平移成非负下标、用二维数组 dp[i][d+offset] 替换哈希,常数更小;值域很大或未知时哈希更稳妥,省去开一个巨大数组和做下标偏移。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 最长等差数列 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。