给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列。例如,[1, 3, 5, 7, 9]、[7, 7, 7, 7] 和 [3, -1, -5, -9] 都是等差序列。再例如,[1, 1, 2, 5, 7] 不是等差序列。数组中的子序列是从数组中删除一些元素(也可能不删除)得到的一个序列。例如,[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。

题目数据保证答案是一个 32-bit 整数。

示例 1:

​ 输入:nums = [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]

示例 2:

​ 输入:nums = [7,7,7,7,7]
​ 输出:16
​ 解释:数组中的任意子序列都是等差子序列。

提示:

​ $1 <= nums.length <= 1000$
​ $-2^{31} <= nums[i] <= 2^{31} - 1$


方法一:动态规划 + 哈希表

我们首先考虑至少有两个元素的等差子序列,下文将其称作弱等差子序列。由于尾项和公差可以确定一个等差数列,因此我们定义状态 $f[i][d]$ 表示尾项为 $nums[i]$,公差为 $d$ 的弱等差子序列的个数。

我们用一个二重循环去遍历 $nums$ 中的所有元素对 $(nums[i], nums[j])$,其中 $j<i$。将 $nums[i]$ 和 $nums[j]$ 分别当作等差数列的尾项和倒数第二项,则该等差数列的公差 $d=nums[i]−nums[j]$。由于公差相同,我们可以将 $nums[i]$ 加到以 $nums[j]$ 为尾项,公差为 $d$ 的弱等差子序列的末尾,这对应着状态转移 $f[i][d]+=f[j][d]$。同时,$(nums[i], nums[j])$ 这一对元素也可以当作一个弱等差子序列,故有状态转移

$$f[i][d] += f[j][d] + 1$$$$f[i][d]+=f[j][d]+1$$

由于题目要统计的等差子序列至少有三个元素,我们回顾上述二重循环,其中「将 $nums[i]$ 加到以 $nums[j]$ 为尾项,公差为 $d$ 的弱等差子序列的末尾」这一操作,实际上就构成了一个至少有三个元素的等差子序列,因此我们将循环中的 $f[j][d]$ 累加,即为答案。

代码实现时,由于 $nums[i]$ 的范围很大,所以计算出的公差的范围也很大,我们可以将状态转移数组 $f$ 的第二维用哈希表代替。

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& nums) {
        int n = nums.size();
        typedef long long LL;
        vector<unordered_map<LL, int>> f(n);
        int res = 0;
        
        for(int i = 0; i < n; i++) {
            for(int k = 0; k < i; k++) {
                LL j = (LL)nums[i] - nums[k];
                auto it = f[k].find(j);
                int t = 0;
                if(it != f[k].end()) {
                    t = it->second;
                    res += t;
                }
                f[i][j] += t + 1;
            }
        }
        return res;
    }
};
Last modification:August 11, 2021
如果觉得我的文章对你有用,请随意赞赏