最长等差数列的变种:最长等差数列的长度与具体序列(允许公差为负且统计最长等差数列的个数)
字数 3248 2025-12-06 12:39:23

最长等差数列的变种:最长等差数列的长度与具体序列(允许公差为负且统计最长等差数列的个数)

题目描述
给你一个整数数组 nums,返回这个数组中所有等差数列的最长长度。同时,如果有多条最长等差数列,你需要统计出最长等差数列的个数。等差数列的定义是:一个序列中任意相邻两项的差相等,这个差值可以是正数、负数或零。注意,等差数列至少需要3个元素。你需要返回一个数组 [length, count],其中 length 是最长等差数列的长度,count 是最长等差数列的个数。

示例
输入:nums = [2,4,6,8,10]
输出:[5, 1]
解释:整个数组就是一个公差为2的等差数列,长度为5,只有这一个最长等差数列。

输入:nums = [7,7,7,7,7]
输出:[5, 1]
解释:整个数组是公差为0的等差数列,长度为5,只有这一个最长等差数列。

输入:nums = [3,6,9,12,15,2,4,6,8,10]
输出:[5, 2]
解释:最长等差数列长度为5,有两个:[3,6,9,12,15][2,4,6,8,10]

解题过程

1. 问题分析
我们需要找出所有可能的等差数列,并记录最长长度及其个数。等差数列由公差 diff 决定,对于任意两个位置 iji < j),diff = nums[j] - nums[i]。一个直观想法是枚举所有数对作为等差数列的最后两项,然后查看前面是否有合适的数字能接上,形成更长的等差数列。

2. 状态定义
定义 dp[i][diff] 为一个哈希表(字典),其中键是公差 diff,值是一个二元组 [len, cnt],表示以第 i 个元素结尾、公差为 diff 的等差数列的长度和个数。注意,这里的“长度”指的是以 i 结尾的等差数列的长度,“个数”指的是以 i 结尾、公差为 diff 的等差数列的个数。

但这样直接存储会面临一个问题:公差可能很大(数值范围大),而且我们需要高效地找到前面某个数字对应的状态。因此,更常见的做法是使用一个数组 dp[i],它是一个哈希表,键是公差,值是 [len, cnt]

3. 状态转移方程
考虑以 nums[i] 结尾的等差数列,我们枚举它前面的一个位置 j0 <= j < i),计算公差 diff = nums[i] - nums[j]

  • 如果存在以 nums[j] 结尾、公差为 diff 的等差数列,那么我们可以接上去,形成更长的等差数列。
  • 具体来说,设从 dp[j] 中查到以 j 结尾、公差为 diff 的等差数列长度为 len_j、个数为 cnt_j,那么接上 nums[i] 后,长度变为 len_j + 1,个数继承为 cnt_j
  • 如果 dp[j] 中没有公差为 diff 的项,说明以 ji 作为最后两项是一个新的等差数列(长度为2),但我们题目要求至少3个元素,所以长度为2的等差数列我们不视为有效等差数列(不参与最长统计)。不过,长度为2的序列可以作为中间状态,用于后续延长。

为了统一处理,我们可以这样设计:
dp[i][diff] 直接表示以 i 结尾、公差为 diff 的等差数列的长度和个数(长度至少为2)。当我们从 j 转移到 i 时:

  • 如果 dp[j] 中有键 diff,设其值为 (len_j, cnt_j),那么 dp[i][diff] 可以更新为 (len_j + 1, cnt_j)
  • 如果 dp[j] 中没有键 diff,那么说明这是一个新的二元组,我们可以初始化 dp[i][diff] = (2, 1)(表示以 ji 开始的等差数列,长度为2,目前有1个这样的序列)。

但注意:当我们统计最终的最长等差数列时,只考虑长度 >=3 的。另外,可能存在多个不同的 j 都能以同样的公差 diff 转移到 i,此时我们需要合并个数(即 cnt 相加)。

4. 合并重复状态
由于不同的 j 可能产生相同的 diff 转移到 i,我们需要在遍历所有 j 时,对每个 diff 合并信息。具体做法:

  • 对于固定的 ij,计算 diff = nums[i] - nums[j]
  • 查看 dp[j] 中是否有 diff
    • 如果有,得到 (len_j, cnt_j),那么候选长度为 len_j + 1,候选个数为 cnt_j
    • 如果没有,则候选长度为 2,候选个数为 1
  • 然后,查看 dp[i] 中是否已经有公差 diff 的条目:
    • 如果没有,则直接插入 (候选长度, 候选个数)
    • 如果有,设已有值为 (len_old, cnt_old),比较候选长度和已有长度:
      • 如果候选长度 > 已有长度,则更新为 (候选长度, 候选个数)
      • 如果候选长度 == 已有长度,则个数累加:cnt_new = cnt_old + 候选个数
      • 如果候选长度 < 已有长度,则忽略。

5. 更新全局答案
在每次更新 dp[i][diff] 后,如果其长度 >=3,我们就用这个长度去更新全局最长长度 max_len,并相应地更新全局个数 max_cnt

  • 如果长度 > max_len,则更新 max_len 为这个长度,max_cnt 为这个个数。
  • 如果长度 == max_len,则 max_cnt 加上这个个数。

6. 边界条件

  • 数组长度小于3时,没有长度 >=3 的等差数列,返回 [0, 0]
  • 初始化:每个位置 idp[i] 是一个空哈希表。

7. 示例推演
nums = [2,4,6,8,10] 为例:

  • i=1(值4):j=0diff=2dp[0] 为空,所以 dp[1][2] = (2,1)
  • i=2(值6):
    • j=0diff=4dp[0] 空,dp[2][4] = (2,1)
    • j=1diff=2dp[1] 中有 (2,1),所以候选 (3,1)dp[2] 中无 diff=2,插入 (3,1)。此时长度3 > 当前全局最大长度3,更新 max_len=3, max_cnt=1
  • i=3(值8):
    • j=0diff=6dp[3][6] = (2,1)
    • j=1diff=4dp[1]diff=4(因为 dp[1] 只有 diff=2),所以 dp[3][4] = (2,1)
    • j=2diff=2dp[2] 中有 (3,1),候选 (4,1)。插入 dp[3][2] = (4,1),更新全局 max_len=4, max_cnt=1
  • 依此类推,最终在 i=4 得到 dp[4][2] = (5,1),全局 max_len=5, max_cnt=1

8. 复杂度分析

  • 时间复杂度:O(n²),因为需要双重循环枚举 (j, i)。哈希表操作可以视为 O(1)。
  • 空间复杂度:O(n²),最坏情况下每个 dp[i] 可能存储 O(i) 个不同的公差。

9. 代码框架(Python风格)

def longestArithSeqLengthAndCount(nums):
    n = len(nums)
    if n < 3:
        return [0, 0]
    dp = [{} for _ in range(n)]  # dp[i][diff] = (length, count)
    max_len, max_cnt = 0, 0
    for i in range(n):
        for j in range(i):
            diff = nums[i] - nums[j]
            length, cnt = 2, 1  # 默认从j,i开始的新序列
            if diff in dp[j]:
                length_j, cnt_j = dp[j][diff]
                length = length_j + 1
                cnt = cnt_j
            # 合并到dp[i]
            if diff in dp[i]:
                len_old, cnt_old = dp[i][diff]
                if length > len_old:
                    dp[i][diff] = (length, cnt)
                elif length == len_old:
                    dp[i][diff] = (length, cnt_old + cnt)
                # 如果length < len_old,保持不变
            else:
                dp[i][diff] = (length, cnt)
            # 更新全局答案(只考虑长度>=3的)
            if length >= 3:
                if length > max_len:
                    max_len = length
                    max_cnt = dp[i][diff][1]  # 注意这里要用dp[i][diff]的个数,因为可能合并过
                elif length == max_len:
                    max_cnt += dp[i][diff][1]
    return [max_len, max_cnt] if max_len >= 3 else [0, 0]

10. 总结
本题是经典最长等差数列问题的扩展,不仅要求最长长度,还要求个数。核心在于在状态中同时记录长度和个数,并在状态转移时正确处理合并逻辑。注意,长度为2的等差数列不参与最终答案统计,但作为中间状态必须保留,以便后续延长。

最长等差数列的变种:最长等差数列的长度与具体序列(允许公差为负且统计最长等差数列的个数) 题目描述 给你一个整数数组 nums ,返回这个数组中所有等差数列的最长长度。同时,如果有多条最长等差数列,你需要统计出最长等差数列的个数。等差数列的定义是:一个序列中任意相邻两项的差相等,这个差值可以是正数、负数或零。注意,等差数列至少需要3个元素。你需要返回一个数组 [length, count] ,其中 length 是最长等差数列的长度, count 是最长等差数列的个数。 示例 输入: nums = [2,4,6,8,10] 输出: [5, 1] 解释:整个数组就是一个公差为2的等差数列,长度为5,只有这一个最长等差数列。 输入: nums = [7,7,7,7,7] 输出: [5, 1] 解释:整个数组是公差为0的等差数列,长度为5,只有这一个最长等差数列。 输入: nums = [3,6,9,12,15,2,4,6,8,10] 输出: [5, 2] 解释:最长等差数列长度为5,有两个: [3,6,9,12,15] 和 [2,4,6,8,10] 。 解题过程 1. 问题分析 我们需要找出所有可能的等差数列,并记录最长长度及其个数。等差数列由公差 diff 决定,对于任意两个位置 i 和 j ( i < j ), diff = nums[j] - nums[i] 。一个直观想法是枚举所有数对作为等差数列的最后两项,然后查看前面是否有合适的数字能接上,形成更长的等差数列。 2. 状态定义 定义 dp[i][diff] 为一个哈希表(字典),其中键是公差 diff ,值是一个二元组 [len, cnt] ,表示以第 i 个元素结尾、公差为 diff 的等差数列的长度和个数。注意,这里的“长度”指的是以 i 结尾的等差数列的长度,“个数”指的是以 i 结尾、公差为 diff 的等差数列的个数。 但这样直接存储会面临一个问题:公差可能很大(数值范围大),而且我们需要高效地找到前面某个数字对应的状态。因此,更常见的做法是使用一个数组 dp[i] ,它是一个哈希表,键是公差,值是 [len, cnt] 。 3. 状态转移方程 考虑以 nums[i] 结尾的等差数列,我们枚举它前面的一个位置 j ( 0 <= j < i ),计算公差 diff = nums[i] - nums[j] 。 如果存在以 nums[j] 结尾、公差为 diff 的等差数列,那么我们可以接上去,形成更长的等差数列。 具体来说,设从 dp[j] 中查到以 j 结尾、公差为 diff 的等差数列长度为 len_j 、个数为 cnt_j ,那么接上 nums[i] 后,长度变为 len_j + 1 ,个数继承为 cnt_j 。 如果 dp[j] 中没有公差为 diff 的项,说明以 j 和 i 作为最后两项是一个新的等差数列(长度为2),但我们题目要求至少3个元素,所以长度为2的等差数列我们不视为有效等差数列(不参与最长统计)。不过,长度为2的序列可以作为中间状态,用于后续延长。 为了统一处理,我们可以这样设计: dp[i][diff] 直接表示以 i 结尾、公差为 diff 的等差数列的长度和个数(长度至少为2)。当我们从 j 转移到 i 时: 如果 dp[j] 中有键 diff ,设其值为 (len_j, cnt_j) ,那么 dp[i][diff] 可以更新为 (len_j + 1, cnt_j) 。 如果 dp[j] 中没有键 diff ,那么说明这是一个新的二元组,我们可以初始化 dp[i][diff] = (2, 1) (表示以 j 和 i 开始的等差数列,长度为2,目前有1个这样的序列)。 但注意:当我们统计最终的最长等差数列时,只考虑长度 >=3 的。另外,可能存在多个不同的 j 都能以同样的公差 diff 转移到 i ,此时我们需要合并个数(即 cnt 相加)。 4. 合并重复状态 由于不同的 j 可能产生相同的 diff 转移到 i ,我们需要在遍历所有 j 时,对每个 diff 合并信息。具体做法: 对于固定的 i 和 j ,计算 diff = nums[i] - nums[j] 。 查看 dp[j] 中是否有 diff : 如果有,得到 (len_j, cnt_j) ,那么候选长度为 len_j + 1 ,候选个数为 cnt_j 。 如果没有,则候选长度为 2 ,候选个数为 1 。 然后,查看 dp[i] 中是否已经有公差 diff 的条目: 如果没有,则直接插入 (候选长度, 候选个数) 。 如果有,设已有值为 (len_old, cnt_old) ,比较候选长度和已有长度: 如果候选长度 > 已有长度,则更新为 (候选长度, 候选个数) 。 如果候选长度 == 已有长度,则个数累加: cnt_new = cnt_old + 候选个数 。 如果候选长度 < 已有长度,则忽略。 5. 更新全局答案 在每次更新 dp[i][diff] 后,如果其长度 >=3,我们就用这个长度去更新全局最长长度 max_len ,并相应地更新全局个数 max_cnt : 如果长度 > max_len ,则更新 max_len 为这个长度, max_cnt 为这个个数。 如果长度 == max_len ,则 max_cnt 加上这个个数。 6. 边界条件 数组长度小于3时,没有长度 >=3 的等差数列,返回 [0, 0] 。 初始化:每个位置 i 的 dp[i] 是一个空哈希表。 7. 示例推演 以 nums = [2,4,6,8,10] 为例: i=1 (值4): j=0 , diff=2 , dp[0] 为空,所以 dp[1][2] = (2,1) 。 i=2 (值6): j=0 , diff=4 , dp[0] 空, dp[2][4] = (2,1) 。 j=1 , diff=2 , dp[1] 中有 (2,1) ,所以候选 (3,1) 。 dp[2] 中无 diff=2 ,插入 (3,1) 。此时长度3 > 当前全局最大长度3,更新 max_len=3, max_cnt=1 。 i=3 (值8): j=0 , diff=6 , dp[3][6] = (2,1) 。 j=1 , diff=4 , dp[1] 无 diff=4 (因为 dp[1] 只有 diff=2 ),所以 dp[3][4] = (2,1) 。 j=2 , diff=2 , dp[2] 中有 (3,1) ,候选 (4,1) 。插入 dp[3][2] = (4,1) ,更新全局 max_len=4, max_cnt=1 。 依此类推,最终在 i=4 得到 dp[4][2] = (5,1) ,全局 max_len=5, max_cnt=1 。 8. 复杂度分析 时间复杂度:O(n²),因为需要双重循环枚举 (j, i) 。哈希表操作可以视为 O(1)。 空间复杂度:O(n²),最坏情况下每个 dp[i] 可能存储 O(i) 个不同的公差。 9. 代码框架(Python风格) 10. 总结 本题是经典最长等差数列问题的扩展,不仅要求最长长度,还要求个数。核心在于在状态中同时记录长度和个数,并在状态转移时正确处理合并逻辑。注意,长度为2的等差数列不参与最终答案统计,但作为中间状态必须保留,以便后续延长。