线性动态规划:等差数列划分
字数 3676 2025-12-19 14:08:59

线性动态规划:等差数列划分

题目描述

给定一个整数数组 nums,返回数组 nums 中所有为等差数列的连续子数组个数。

等差数列定义:一个数列中,任意相邻两项的差相等,这个固定的差称为公差。例如,[1, 3, 5, 7] 是公差为2的等差数列。

示例

输入:nums = [1, 2, 3, 4]
输出:3
解释:nums 中有三个子数组是等差数列:
[1, 2, 3]
[2, 3, 4]
[1, 2, 3, 4]

注意

  • 子数组必须是连续的,且长度至少为3。
  • 数组长度范围在 [1, 5000] 之间,元素范围为 [-1000, 1000]

解题思路详解

1. 问题分析

我们需要统计数组中所有长度 ≥3 的连续等差数列子数组的个数。
一个直观的想法是,遍历所有可能的子数组,检查其是否为等差数列。但这样时间复杂度为 O(n³),会超时(n 最大 5000)。

核心观察

  • 如果已知一个较长的等差数列,那么它的所有连续子段(长度 ≥3)也都是等差数列吗?
    不一定!例如 [1, 2, 3, 4] 是等差数列,其子数组 [2, 3, 4] 也是,但更短的子数组如 [1, 2, 3] 也是。
    实际上,一个长度为 L 的等差数列,包含的长度 ≥3 的连续子数组个数是 (L-2) + (L-3) + ... + 1 = (L-1)(L-2)/2
    但直接按长度统计会重复计数,因为不同位置的等差数列会重叠。

更好的思路是动态规划,利用“以某个位置结尾的等差数列”这一状态,避免重复计算。


2. 动态规划定义状态

dp[i] 表示:以第 i 个元素结尾的、长度 ≥3 的连续等差数列的个数
但这定义不方便直接递推,因为“长度 ≥3”这个条件在递推时需要特殊处理。

换个角度,定义:

diff[i] = nums[i] - nums[i-1]   // 相邻两项的差

然后定义:

dp[i] = 以 nums[i] 结尾的、长度 ≥2 的连续等差数列的最大长度(即这个等差数列的元素个数)?

不,更好的定义是:

dp[i] = 以 nums[i] 结尾的、且与前一个元素 nums[i-1] 能构成等差数列的连续子数组的“等差增量计数”。

更准确的标准定义(来自经典的解法):

dp[i] = 以 nums[i] 结尾的、长度至少为 3 的等差数列的个数。

但这样递推困难,因为需要知道前一个位置是否构成等差。

更常用且简单的状态定义
设:

dp[i] = 以 nums[i] 结尾的连续等差数列的“潜在长度计数”,但我们需要长度至少为3时才计入答案。

实际上,更直接的方法是:
d[i] 表示以 nums[i] 结尾的、且公差为某个值的连续等差数列的“扩展计数”。


标准状态定义
定义 dp[i] 为:以 nums[i] 结尾的、且与 nums[i-1] 的差等于整个等差数列的公差的连续等差数列的最大长度(即这个等差数列的元素个数)

更具体地:

  • 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2],说明 nums[i-2], nums[i-1], nums[i] 能构成一个长度为 3 的等差数列,那么以 nums[i] 结尾的等差数列长度 = 以 nums[i-1] 结尾的等差数列长度 + 1。
  • 否则,以 nums[i] 结尾的等差数列长度为 2(即只包含 nums[i-1] 和 nums[i],但长度 2 不算有效等差数列)。

用公式表示:

设 diff = nums[i] - nums[i-1]
如果 diff == nums[i-1] - nums[i-2](i ≥ 2),
则 dp[i] = dp[i-1] + 1
否则 dp[i] = 0

这里 dp[i] 的含义是:以 nums[i] 结尾的、且长度至少为 3 的等差数列的“新增数量”
仔细看,dp[i] 实际上是“新增的等差数列个数”,但这个“新增”是指以 i 结尾的等差数列中,长度 ≥3 的那些。


更准确的理解(这是经典解法):
定义 dp[i] 表示:以 nums[i] 结尾的、长度至少为 3 的等差数列的个数。
那么如何递推?

  • 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2],说明从 i-2 到 i 这三个数构成等差数列。
    那么,所有以 i-1 结尾的长度 ≥3 的等差数列,在末尾加上 nums[i] 后,仍然构成等差数列,并且还新增了一个长度为 3 的等差数列(即 i-2, i-1, i 这三个数)。
    所以 dp[i] = dp[i-1] + 1
  • 否则,dp[i] = 0

例子
nums = [1, 2, 3, 4]
i=2: nums[2]-nums[1]=1, nums[1]-nums[0]=1,相等,所以 dp[2] = dp[1] + 1 = 0 + 1 = 1,表示以 3 结尾的长度 ≥3 的等差数列有 1 个:[1,2,3]。
i=3: 4-3=1, 3-2=1,相等,dp[3] = dp[2] + 1 = 1 + 1 = 2,表示以 4 结尾的长度 ≥3 的等差数列有 2 个:[2,3,4] 和 [1,2,3,4]。

最后答案是 sum(dp[0..n-1])。


3. 状态转移方程

  • 初始化:dp[0] = 0, dp[1] = 0,因为长度至少为 3。
  • 对于 i ≥ 2:
    • 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2],则 dp[i] = dp[i-1] + 1
    • 否则 dp[i] = 0
  • 结果 = Σ dp[i] (i 从 0 到 n-1)

为什么 dp[i] = dp[i-1] + 1
因为当 i 能与前两个数构成等差数列时,所有以 i-1 结尾的长度 ≥3 的等差数列,在末尾添加 nums[i] 后仍然保持等差性质(公差不变),所以数量继承 dp[i-1]。
同时,新增了一个长度为 3 的等差数列 [i-2, i-1, i],所以额外 +1。


4. 例子推导

nums = [1, 2, 3, 4, 5]

i nums[i] diff = nums[i]-nums[i-1] 与前一个 diff 相等? dp[i] 解释
0 1 - - 0 初始化
1 2 1 - 0 初始化
2 3 1 相等(i=2, diff=1, prev_diff=1) 1 新增 [1,2,3]
3 4 1 相等 dp[2]+1=2 新增 [2,3,4] 和 [1,2,3,4]
4 5 1 相等 dp[3]+1=3 新增 [3,4,5], [2,3,4,5], [1,2,3,4,5]

dp 数组 = [0, 0, 1, 2, 3]
总和 = 1+2+3 = 6。
验证:长度 ≥3 的等差数列子数组有:

  • 长度3: [1,2,3], [2,3,4], [3,4,5] → 3个
  • 长度4: [1,2,3,4], [2,3,4,5] → 2个
  • 长度5: [1,2,3,4,5] → 1个
    总数 3+2+1=6,与动态规划结果一致。

5. 算法步骤

  1. 如果数组长度 n < 3,直接返回 0。
  2. 初始化 dp 数组长度为 n,dp[0]=0, dp[1]=0。
  3. 初始化结果 total = 0。
  4. 从 i=2 到 n-1:
    • 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:
      • dp[i] = dp[i-1] + 1
      • total += dp[i]
    • 否则:
      • dp[i] = 0
  5. 返回 total。

实际上,dp[i] 只依赖于 dp[i-1],所以可以用一个变量 current 代替 dp 数组,优化空间复杂度为 O(1)。


6. 优化空间复杂度

用变量 curr 表示当前的 dp[i]:

  • 初始化 curr = 0, total = 0
  • 从 i=2 到 n-1:
    • 如果满足等差条件,curr = curr + 1,否则 curr = 0
    • total += curr

最终代码(Python):

def numberOfArithmeticSlices(nums):
    n = len(nums)
    if n < 3:
        return 0
    total = 0
    curr = 0
    for i in range(2, n):
        if nums[i] - nums[i-1] == nums[i-1] - nums[i-2]:
            curr += 1
            total += curr
        else:
            curr = 0
    return total

时间复杂度:O(n),只遍历一次数组。
空间复杂度:O(1),只用了常数个变量。


总结

这个问题的动态规划思路是:

  • 定义 dp[i] 为以 nums[i] 结尾的长度 ≥3 的等差数列个数。
  • 状态转移基于相邻三数的差是否相等,若相等则 dp[i] = dp[i-1] + 1
  • 最终答案是所有 dp[i] 的总和。

这个方法巧妙利用了“等差数列的扩展性质”,将问题转化为简单的线性 DP,且可以优化到 O(1) 空间。

线性动态规划:等差数列划分 题目描述 给定一个整数数组 nums ,返回数组 nums 中所有为 等差数列 的连续子数组个数。 等差数列 定义:一个数列中,任意相邻两项的差相等,这个固定的差称为公差。例如, [1, 3, 5, 7] 是公差为2的等差数列。 示例 : 注意 : 子数组必须是连续的,且长度至少为3。 数组长度范围在 [1, 5000] 之间,元素范围为 [-1000, 1000] 。 解题思路详解 1. 问题分析 我们需要统计数组中所有长度 ≥3 的连续等差数列子数组的个数。 一个直观的想法是,遍历所有可能的子数组,检查其是否为等差数列。但这样时间复杂度为 O(n³),会超时(n 最大 5000)。 核心观察 : 如果已知一个较长的等差数列,那么它的所有连续子段(长度 ≥3)也都是等差数列吗? 不一定!例如 [1, 2, 3, 4] 是等差数列,其子数组 [2, 3, 4] 也是,但更短的子数组如 [1, 2, 3] 也是。 实际上, 一个长度为 L 的等差数列,包含的长度 ≥3 的连续子数组个数是 (L-2) + (L-3) + ... + 1 = (L-1)(L-2)/2 。 但直接按长度统计会重复计数,因为不同位置的等差数列会重叠。 更好的思路是 动态规划 ,利用“以某个位置结尾的等差数列”这一状态,避免重复计算。 2. 动态规划定义状态 设 dp[i] 表示: 以第 i 个元素结尾的、长度 ≥3 的连续等差数列的个数 。 但这定义不方便直接递推,因为“长度 ≥3”这个条件在递推时需要特殊处理。 换个角度,定义: 然后定义: 不,更好的定义是: 更准确的标准定义(来自经典的解法): 但这样递推困难,因为需要知道前一个位置是否构成等差。 更常用且简单的状态定义 : 设: 实际上,更直接的方法是: 设 d[i] 表示以 nums[i] 结尾的、且公差为某个值的连续等差数列的“扩展计数”。 标准状态定义 : 定义 dp[i] 为: 以 nums[ i] 结尾的、且与 nums[ i-1] 的差等于整个等差数列的公差的连续等差数列的最大长度(即这个等差数列的元素个数) 。 更具体地: 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2] ,说明 nums[i-2], nums[i-1], nums[i] 能构成一个长度为 3 的等差数列,那么以 nums[ i] 结尾的等差数列长度 = 以 nums[ i-1 ] 结尾的等差数列长度 + 1。 否则,以 nums[ i] 结尾的等差数列长度为 2(即只包含 nums[ i-1] 和 nums[ i ],但长度 2 不算有效等差数列)。 用公式表示: 这里 dp[ i] 的含义是:以 nums[ i] 结尾的、且长度至少为 3 的等差数列的“新增数量” ? 仔细看,dp[ i ] 实际上是“新增的等差数列个数”,但这个“新增”是指以 i 结尾的等差数列中,长度 ≥3 的那些。 更准确的理解 (这是经典解法): 定义 dp[i] 表示:以 nums[i] 结尾的、长度至少为 3 的等差数列的个数。 那么如何递推? 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2] ,说明从 i-2 到 i 这三个数构成等差数列。 那么,所有以 i-1 结尾的长度 ≥3 的等差数列,在末尾加上 nums[ i ] 后,仍然构成等差数列,并且还新增了一个长度为 3 的等差数列(即 i-2, i-1, i 这三个数)。 所以 dp[i] = dp[i-1] + 1 。 否则, dp[i] = 0 。 例子 : nums = [ 1, 2, 3, 4 ] i=2: nums[ 2]-nums[ 1]=1, nums[ 1]-nums[ 0]=1,相等,所以 dp[ 2] = dp[ 1] + 1 = 0 + 1 = 1,表示以 3 结尾的长度 ≥3 的等差数列有 1 个:[ 1,2,3 ]。 i=3: 4-3=1, 3-2=1,相等,dp[ 3] = dp[ 2] + 1 = 1 + 1 = 2,表示以 4 结尾的长度 ≥3 的等差数列有 2 个:[ 2,3,4] 和 [ 1,2,3,4 ]。 最后答案 是 sum(dp[ 0..n-1 ])。 3. 状态转移方程 初始化: dp[0] = 0, dp[1] = 0 ,因为长度至少为 3。 对于 i ≥ 2: 如果 nums[i] - nums[i-1] == nums[i-1] - nums[i-2] ,则 dp[i] = dp[i-1] + 1 否则 dp[i] = 0 结果 = Σ dp[ i ] (i 从 0 到 n-1) 为什么 dp[ i] = dp[ i-1] + 1 ? 因为当 i 能与前两个数构成等差数列时,所有以 i-1 结尾的长度 ≥3 的等差数列,在末尾添加 nums[ i] 后仍然保持等差性质(公差不变),所以数量继承 dp[ i-1 ]。 同时,新增了一个长度为 3 的等差数列 [ i-2, i-1, i ],所以额外 +1。 4. 例子推导 nums = [ 1, 2, 3, 4, 5 ] | i | nums[ i] | diff = nums[ i]-nums[ i-1] | 与前一个 diff 相等? | dp[ i ] | 解释 | |---|---------|--------------------------|-------------------|-------|------| | 0 | 1 | - | - | 0 | 初始化 | | 1 | 2 | 1 | - | 0 | 初始化 | | 2 | 3 | 1 | 相等(i=2, diff=1, prev_ diff=1)| 1 | 新增 [ 1,2,3 ] | | 3 | 4 | 1 | 相等 | dp[ 2]+1=2 | 新增 [ 2,3,4] 和 [ 1,2,3,4 ] | | 4 | 5 | 1 | 相等 | dp[ 3]+1=3 | 新增 [ 3,4,5], [ 2,3,4,5], [ 1,2,3,4,5 ] | dp 数组 = [ 0, 0, 1, 2, 3 ] 总和 = 1+2+3 = 6。 验证:长度 ≥3 的等差数列子数组有: 长度3: [ 1,2,3], [ 2,3,4], [ 3,4,5 ] → 3个 长度4: [ 1,2,3,4], [ 2,3,4,5 ] → 2个 长度5: [ 1,2,3,4,5 ] → 1个 总数 3+2+1=6,与动态规划结果一致。 5. 算法步骤 如果数组长度 n < 3,直接返回 0。 初始化 dp 数组长度为 n,dp[ 0]=0, dp[ 1 ]=0。 初始化结果 total = 0。 从 i=2 到 n-1: 如果 nums[ i] - nums[ i-1] == nums[ i-1] - nums[ i-2 ]: dp[ i] = dp[ i-1 ] + 1 total += dp[ i ] 否则: dp[ i ] = 0 返回 total。 实际上,dp[ i] 只依赖于 dp[ i-1],所以可以用一个变量 current 代替 dp 数组,优化空间复杂度为 O(1)。 6. 优化空间复杂度 用变量 curr 表示当前的 dp[ i ]: 初始化 curr = 0 , total = 0 。 从 i=2 到 n-1: 如果满足等差条件, curr = curr + 1 ,否则 curr = 0 。 total += curr 。 最终代码 (Python): 时间复杂度 :O(n),只遍历一次数组。 空间复杂度 :O(1),只用了常数个变量。 总结 这个问题的动态规划思路是: 定义 dp[i] 为以 nums[ i ] 结尾的长度 ≥3 的等差数列个数。 状态转移基于相邻三数的差是否相等,若相等则 dp[i] = dp[i-1] + 1 。 最终答案是所有 dp[ i ] 的总和。 这个方法巧妙利用了“等差数列的扩展性质”,将问题转化为简单的线性 DP,且可以优化到 O(1) 空间。