LeetCode 446. 等差数列划分 II - 子序列
题目描述


题意分析
统计所有长度至少为
3的等差子序列。选择的下标必须递增,但可以跳过元素;只要下标组合不同,就算不同子序列,即使对应数值完全相同也要分别计数。公差可以为正、负或零。题目保证最终答案可用
32位整数表示,但两个元素的差可能超出这个范围,因此公差要使用64位整数。
解法:按结尾和公差哈希 DP
核心思路
[!blue]
要给一个等差子序列追加新元素,需要知道它的最后一个位置和公差。定义
dp[i][d]为“以下标i结尾、公差为d、长度至少为2”的子序列数量。状态刻意包含长度为2的序列,因为它们是以后组成合法三项序列的起点。按最后两个下标
j < i枚举,公差只能是d = nums[i] - nums[j]。设count = dp[j][d],其中每个序列都能追加nums[i],并仍然保持公差d。追加后长度至少为3,所以这count个新序列既加入dp[i][d],也加入最终答案。此外,单独选择
j、i就组成一个新的二项序列,只能作为后续扩展的状态,当前还不能计入答案。因此转移是dp[i][d] += count + 1,而答案只增加count。不同前驱
j对应不同的倒数第二个下标,扩展出的序列不会重复;同一个前驱中不同的下标组合也会在追加后保持不同。每个合法序列都在处理其最后一个下标时被计入一次,因此这项累加既完整也不重计。
解题步骤
- 为每个结尾下标建立一张空哈希表,未出现过的公差计数视为
0。- 从左到右枚举末项下标
i,再枚举全部j < i,保证前驱状态已经计算完毕。- 先把元素转换为宽整数,再计算公差
diff,读取count = dp[j][diff]。- 将
count加入答案,并把count + 1累加到dp[i][diff]。- 返回答案。元素不足三个时,只会建立二项状态,不会给答案增加任何合法序列。
代码实现
class Solution {
// dp[i][diff] 表示以 nums[i] 结尾、公差为 diff、长度至少为 2 的子序列数量。
public int numberOfArithmeticSlices(int[] nums) {
int n = nums.length;
List<Map<Long, Integer>> dp = new ArrayList<>();
for (int i = 0; i < n; i++) {
dp.add(new HashMap<>());
}
long answer = 0;
for (int i = 0; i < n; i++) {
Map<Long, Integer> cur = dp.get(i);
for (int j = 0; j < i; j++) {
long diff = (long) nums[i] - nums[j];
int count = dp.get(j).getOrDefault(diff, 0);
// 只有继承的序列接上当前项后长度至少三,计入答案
answer += count;
// 新增的一只代表二项序列,暂存状态供后续扩展
cur.put(diff, cur.getOrDefault(diff, 0) + count + 1);
}
}
return (int) answer;
}
}
func numberOfArithmeticSlices(nums []int) int {
// dp[i][diff] 表示以 nums[i] 结尾、公差为 diff、长度至少为 2 的子序列数量。
n := len(nums)
dp := make([]map[int64]int, n)
for i := 0; i < n; i++ {
dp[i] = make(map[int64]int)
}
res := 0
for i := 0; i < n; i++ {
for j := 0; j < i; j++ {
diff := int64(nums[i]) - int64(nums[j])
count := dp[j][diff]
// 只有继承的序列接上当前项后长度至少三,计入答案
res += count
// 新增的一只代表二项序列,暂存状态供后续扩展
dp[i][diff] = dp[i][diff] + count + 1
}
}
return res
}
复杂度分析
- 时间复杂度:期望 $O(n^2)$,枚举全部前后下标对。
- 空间复杂度:$O(n^2)$,各结尾的不同公差状态总数至多平方级。
关键点总结
[!green]
- 状态包含长度为
2的序列,答案只统计长度至少为3的序列,两者范围不同。+1是新建二项序列,继承的count才是已经达到题目长度要求的新答案。- 同一结尾、同一公差可以由多个前驱产生,必须累加方案数。
- 以最后两个下标划分来源,让每个合法序列都有唯一的计数时刻。
易错点总结
[!yellow]
- 把
count + 1全部加入答案,会把长度为2的序列错误算入。- 先排序或去重都会改变下标顺序或丢失不同选择,不能为了处理公差而修改原数组。
- 只保存全局公差计数会丢失结尾位置,无法判断哪些序列可以向当前下标扩展。
- 公差要先扩大操作数类型再相减;差值已经溢出后再转成宽整数,无法恢复原值。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 413. 等差数列划分 | 中等 | 连续区间只需比较相邻差值,本题允许跳过元素,需要为每个末项保存各公差的方案数。 |
| 1027. 最长等差数列 | 中等 | 同样按末项和公差建立状态,原题取最长长度,本题累加所有长度至少3的方案。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!