https://leetcode-cn.com/problems/arithmetic-slices-ii-subsequence/solution/deng-chai-shu-lie-hua-fen-ii-zi-xu-lie-by-leetcode/
如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
例如,以下数列为等差数列:
1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9
以下数列不是等差数列。
1, 1, 2, 5, 7
数组 A 包含 N 个数,且索引从 0 开始。该数组子序列将划分为整数序列 (P0, P1, ..., Pk),P 与 Q 是整数且满足 0 ≤ P0 < P1 < ... < Pk < N。
如果序列 A[P0],A[P1],...,A[Pk-1],A[Pk] 是等差的,那么数组 A 的子序列 (P0,P1,…,PK) 称为等差序列。值得注意的是,这意味着 k ≥ 2。
函数要返回数组 A 中所有等差子序列的个数。
输入包含 N 个整数。每个整数都在 -231 和 231-1 之间,另外 0 ≤ N ≤ 1000。保证输出小于 231-1。
示例:
输入:[2, 4, 6, 8, 10]
输出:7
解释:
所有的等差子序列为:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]
方法二:动态规划【通过】
直觉
要决定一个等差数列,至少需要两个参数:序列的首(尾)项和公差。
算法
从这一点出发,我们可以简单地设计出状态:
f[i][d] ,代表以 A[i] 结束且公差为 d 的等差数列个数。
让我们来试着列出状态转移方程。假设我们想在现有的等差数列们的基础上添加一个新元素 A[i] 来得到新的子序列。只有当现有的等差数列最后一项加上公差等于 A[i] 时,才能够将该元素添加到等差数列后。
于是,我们可以写出 关于A[i] 的状态转移方程:
对于所有 j < i,f[i][A[i] - A[j]] += f[j][A[i] - A[j]]。
这表示了添加新项得到新的等差数列的过程。
但问题来了。初始时所有的 f[i][d] 都置为 0,如果一开始根本就没有子序列,怎么生成新的子序列呢?
在等差数列的原定义中,子序列的长度至少为 3。这使得只有两个下标 i 和 j 时,很难生成新的序列。那么,能不能考虑将长度为 2 的序列纳入考虑呢?
我们定义弱等差数列:
弱等差数列 是至少有两个元素的子序列,任意两个连续元素的差相等。
弱等差数列有两个十分有用的性质?
对于任何 i, j (i != j),A[i] 和 A[j] 总能组成一个弱等差数列。
若在弱等差数列后添加一个元素且保持等差,则一定得到一个等差数列。
第二个性质很显而易见,因为弱等差数列和等差数列的唯一区别就是它们的长度。
于是,我们可以修改状态的定义:
f[i][d]代表以 A[i] 结束且公差为 d 的弱等差数列个数。
现在状态转移方程就十分简单:
对于所有 j < i,f[i][A[i] - A[j]] += (f[j][A[i] - A[j]] + 1)。
这里的 1 是因为根据性质一,我们可以对 (i, j)生成一个新的弱等差数列。
所有弱等差数列的数量即为 f[i][d]之和。但如何从中挑选出不弱的那些呢?
有两种方法:
其一,我们可以对真弱等差数列的数量进行直接计数。真弱等差数列即为长度为 2 的弱等差数列,故其数量为 (i, j) 对的格数,即为 \binom{n}{2} = \frac{n * (n - 1)}{2}。(
2
n
)=
2
n∗(n−1)
。
其二,对于 f[i][A[i] - A[j]] += (f[j][A[i] - A[j]] + 1),f[j][A[i] - A[j]] 是现有的弱等差数列个数,而 1 是根据 A[i] 和 A[j] 新建的子序列。根据性质二,新增加的序列必为等差数列。故 f[j][A[i] - A[j]] 为新生成的等差数列的个数。
可以用下面的示例来演示整个过程:
[1, 1, 2, 3, 4, 5]
计算上述序列的答案。
对于第一个元素 1,前面没有元素,答案保持 0。
对于第二个元素 1,该元素与上一个 1 可以组成公差 0 的弱等差数列: [1, 1]。
对于第三个元素 2,它无法与唯一的弱等差数列 [1, 1] 合并,因此答案仍为 0。与上一个元素类似,它也可以形成新的弱等差数列 [1, 2] 和 [1, 2]。
对于第四个元素 3,若我们将它添加到最后元素为 2 的序列中,则其公差必为 3 - 2 = 1。[1, 2] 和 [1, 2] 符合要求,故将 3 添加到这些序列后,答案增加 2。Similar to above, 与上一个元素类似,它也可以形成新的弱等差数列 [1, 3],[1, 3] 和 [2, 3]。
其他元素以此类推,可参见下图。红色括号表示长度为 2 的弱等差数列,黑色括号表示等差数列。答案为黑色括号的数量。
class Solution {
public int numberOfArithmeticSlices(int[] A) {
int n = A.length;
long ans = 0;
Map[] cnt = new Map[n];
for (int i = 0; i < n; i++) {
cnt[i] = new HashMap<>(i);
for (int j = 0; j < i; j++) {
long delta = (long)A[i] - (long)A[j];
if (delta < Integer.MIN_VALUE || delta > Integer.MAX_VALUE) {
continue;
}
int diff = (int)delta;
int sum = cnt[j].getOrDefault(diff, 0);
int origin = cnt[i].getOrDefault(diff, 0);
cnt[i].put(diff, origin + sum + 1);
ans += sum;
}
}
return (int)ans;
}
在线java https://tool.lu/coderunner/
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
class main {
public static void main(String[] args) {
int[] A = {1, 1, 2, 3, 4, 5};
int n = A.length;
long ans = 0;
Map[] cnt = new Map[n];
for (int i = 0; i < n; i++) {
cnt[i] = new HashMap<>(i);
System.out.println(i+ "a");
for (int j = 0; j < i; j++) {
long delta = (long)A[i] - (long)A[j];
if (delta < Integer.MIN_VALUE || delta > Integer.MAX_VALUE) {
continue;
}
int diff = (int)delta;
System.out.println(diff+"diff");
int sum = cnt[j].getOrDefault(diff, 0);
int origin = cnt[i].getOrDefault(diff, 0);
System.out.println(origin+"origin");
System.out.println(sum+"sum");
cnt[i].put(diff, origin + sum + 1);
ans += sum;
}
}
System.out.println(ans);
}
}