等差数列划分 图解题解
这道题到底在问什么
- 输入
- nums=[1,2,3,4]
- 输出
- 3([1,2,3]、[2,3,4]、[1,2,3,4])
- 输入
- nums=[1,3,5,7,9]
- 输出
- 6(整段等差,各长度子段共 6 个)
最优解:为什么这么做
一句话答案:LeetCode 413 等差数列划分:数长度≥3 的连续等差子数组个数。dp[i] 记以 i 结尾的等差子段数,同公差就 dp[i]=dp[i-1]+1,否则 0,答案累加所有 dp[i],一遍扫描 O(n) 时间、O(1) 空间。
等差数列划分这道题到底在数什么
给整数数组 nums,数出长度至少 3 的连续子数组里有多少段是等差数列(相邻两项的差处处相等)。要的是段数,不是逐一列出。nums=[1,2,3,4] 有 3 段:1,2,3、2,3,4、1,2,3,4。
为什么把所有连续子数组都查一遍会太慢
枚举每个连续子数组再核对是不是等差:长度 n 约有 n 平方除以 2 个子数组,边扫边判也要 O(n 平方),且重复劳动明显——核对以某位置结尾的长段时,它前面那截短段刚被核对过。这种「短段被长段重算」正是递推能省的地方;递推即拿前面算好的结果推出当前值。
dp[i] 定成以 i 结尾的等差段数
不数所有子数组,只盯结尾。定义 dp[i] 为「以 nums[i] 结尾的等差连续子段有几个」,这样每段等差子数组都有唯一结尾下标,所有 dp[i] 相加就不重不漏数完全部段。能否延续只看最近三数:若 nums[i] 减 nums[i-1] 等于 nums[i-1] 减 nums[i-2],公差(相邻两数的差)接上、dp[i] 有值,否则断开、dp[i]=0。要三个数才能比两个差,故从下标 2 起判。
同公差时为什么是 dp[i-1]+1 而不是从头数
公差延续时 dp[i]=dp[i-1]+1。把「以 i 结尾的等差段」拆成两拨。第一拨是所有以 i-1 结尾的等差段各自向右接上 nums[i]:末尾添个同公差的数仍是等差,只是结尾从 i-1 挪到 i,恰好 dp[i-1] 个。第二拨是一个全新最短段 nums[i-2]、nums[i-1]、nums[i]。合起来 dp[i-1]+1,没有第三种——dp[i-1] 早把结尾在 i-1 的段数清了,拿来延伸即可,不必从头重数。
每算一格把 dp[i] 累加进答案即总段数;同公差加的是 dp[i] 不是 1,因一次延伸新增好几段。
拿 [1,3,5,7,9] 把 dp 一格格填出来
从下标 2 起。值 5:5 减 3 和 3 减 1 都是 2,dp[2]=dp[1]+1=0+1=1,答案 1,新段 1,3,5。值 7:差都 2,dp[3]=dp[2]+1=2,答案 1+2=3,这 2 段是 1,3,5 延伸成 1,3,5,7 加新段 3,5,7。值 9:差都 2,dp[4]=dp[3]+1=3,答案 3+3=6,3 段是延伸的 1,3,5,7,9、3,5,7,9 加新段 5,7,9。dp 依次 1、2、3,答案 6,对上题面:dp 值本身每步只涨 1(1、2、3),把这三个 dp 值累加才凑成答案 6。
复杂度多少,不足 3 个和断点归零别写错
只从下标 2 扫到末尾一遍,每步一次比较加常数次加法,时间 O(n);dp[i] 只用 dp[i-1],压成一个滚动变量 cur 即可(只留最近一项、循环覆盖旧值),空间 O(1)。
两处边界。一是不足 3 个数:长度小于 3 时凑不出段,答案 0,循环从下标 2 起天然进不去。二是断点后别把 cur 写反:公差一断 cur 必须归 0,因为以当前位置结尾的段全断了、都延续不了;此时不从单个数从头起,而是往后扫、拿最近两数当候选,下个差对上再从 1 长起。
▶ 动画逐步走查(共 22 步)——想跟着动画一帧帧对照就展开
- 3记住「同公差就 cur+1 并 ans+=cur,断了就 cur 归 0」,下面每一帧都在套它。
- 4开局:前两个数还凑不成长度 3 的子段,cur=0、ans=0。从下标 2 起,每一步看 nums[i] 与前两个数是否构成等差。
- 5看下标 2(值 3):它和前一个的差是 1,前两个之间的差是 1。两差相等,公差延续。
- 6公差延续:cur 加到 1(以 3 结尾,新增了 1 个等差子段),ans 累加到 1。绿色是当前这条等差段。
- 7这里只新增了 1 个长度 3 的段 [1, 2, 3](高亮),它给 ans 贡献 +1。
- 8看下标 3(值 4):它和前一个的差是 1,前两个之间的差是 1。两差相等,公差延续。
- 9公差延续:cur 加到 2(以 4 结尾,新增了 2 个等差子段),ans 累加到 3。绿色是当前这条等差段。
- 10这 2 个新段里最短的是 [2, 3, 4](高亮),另一个更长的是 [1, 2, 3, 4],都是把它向左延伸而来;它们正好凑成这一步给 ans 的 +2。
- 11看下标 4(值 5):它和前一个的差是 1,前两个之间的差是 1。两差相等,公差延续。
- 12公差延续:cur 加到 3(以 5 结尾,新增了 3 个等差子段),ans 累加到 6。绿色是当前这条等差段。
- 13这 3 个新段里最短的是 [3, 4, 5](高亮),更长的 2 个是 [2, 3, 4, 5]、[1, 2, 3, 4, 5],都是把它向左延伸而来;它们正好凑成这一步给 ans 的 +3。
- 14看下标 5(值 9):它和前一个的差是 4,前两个之间的差是 1。两差不等,等差在这里断了。
- 15公差中断:cur 归 0,ans 保持 6 不变。注意不是从单个 9 重新开始,而是继续往后扫,用最近相邻的两个数当候选,看下一步的差能否重新连成等差段。
- 16看下标 6(值 8):它和前一个的差是 -1,前两个之间的差是 4。两差不等,等差在这里断了。
- 17公差中断:cur 归 0,ans 保持 6 不变。注意不是从单个 8 重新开始,而是继续往后扫,用最近相邻的两个数当候选,看下一步的差能否重新连成等差段。
- 18看下标 7(值 7):它和前一个的差是 -1,前两个之间的差是 -1。两差相等,公差延续。
- 19公差延续:cur 加到 1(以 7 结尾,新增了 1 个等差子段),ans 累加到 7。绿色是当前这条等差段。
- 20这里只新增了 1 个长度 3 的段 [9, 8, 7](高亮),它给 ans 贡献 +1。
- 21看下标 8(值 6):它和前一个的差是 -1,前两个之间的差是 -1。两差相等,公差延续。
- 22公差延续:cur 加到 2(以 6 结尾,新增了 2 个等差子段),ans 累加到 9。绿色是当前这条等差段。
- 23这 2 个新段里最短的是 [8, 7, 6](高亮),另一个更长的是 [9, 8, 7, 6],都是把它向左延伸而来;它们正好凑成这一步给 ans 的 +2。
- 24扫完整个数组,ans = 9。其中开头 [1,2,3,4,5] 贡献 6 个、结尾 [9,8,7,6] 贡献 3 个,中间断开不贡献,合计 9。
⚠️ 容易写错的地方
✗ 错:同公差时只给 ans 加 1
✓ 对:ans 要加 cur(累加当前段数)
多接一个同公差元素,会新增「以它结尾」的多个等差段(长度 3、4、…),个数正好是 cur;只加 1 会漏数较长的子段
✗ 错:断点时忘了把 cur 归 0
✓ 对:差不相等就 cur = 0
cur 是「以当前位置结尾的连续等差段数」,一旦断开,之前的段都不能延续,必须清零重新计
✗ 错:从下标 0 或 1 开始判断
✓ 对:从下标 2 起(才有三个数)
等差子段至少长 3,需要 nums[i]、nums[i−1]、nums[i−2] 三个数才能比较两个差
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
cur = ans = 0
for i in range(2, len(nums)):
if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:
cur += 1
ans += cur
else:
cur = 0
return ansC++
#include <vector>
using namespace std;
class Solution {
public:
int numberOfArithmeticSlices(vector<int>& nums) {
int cur = 0, ans = 0;
for (int i = 2; i < (int)nums.size(); ++i) {
if (nums[i] - nums[i-1] == nums[i-1] - nums[i-2]) ans += ++cur;
else cur = 0;
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int numberOfArithmeticSlices(int[] nums) {
int cur = 0, ans = 0;
for (int i = 2; i < nums.length; i++) {
if (nums[i] - nums[i-1] == nums[i-1] - nums[i-2]) ans += ++cur;
else cur = 0;
}
return ans;
}
}复杂度
时间
O(n)
n 是数组长度。只从下标 2 扫到末尾一遍,每步常数操作
空间
O(1)
只用 cur、ans 两个变量,不开额外数组
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 等差数列划分 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么可以只用一个变量 cur,而不开一整个 dp 数组?+
完整形态是 dp[i] 表示以 nums[i] 结尾的等差段数:同公差时 dp[i]=dp[i-1]+1,否则 0,答案是所有 dp[i] 之和。注意 dp[i] 只用到紧挨的 dp[i-1],更早的值再也用不上,所以没必要留整个数组——用一个变量 cur 记住「上一格」,每步更新它、并把它累加进答案即可,空间从 O(n) 降到 O(1),这就是参考代码的写法。
如果改成只数长度恰好为 k 的等差子数组呢?+
累加规则要改。维护当前连续等差段的元素个数 len,开始和每次断开时都把 len 重置为 2(相邻两数先凑成候选),同公差就 len 加 1。到某个位置,只要 len 不小于 k,就说明存在一个以它结尾、长度恰为 k 的段(从当前往左数 k 个),答案加 1。本题数的是长度不小于 3 的全部,所以用 cur 把各种长度一次累加;定长 k 则每步至多加 1。
数组里所有数都相等,比如 7,7,7,算不算等差?+
算。等差只要求相邻差处处相等,公差是 0 也满足,7 减 7 等于 0 等于 7 减 7。所以 7,7,7 是一段等差、贡献 1,更长的全相等数组照样按 dp[i]=dp[i-1]+1 累加。判断时别额外把公差为 0 的情况排除掉,否则会漏数。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 等差数列划分 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
把这道题真正拿下
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。