赞
踩
目录
难度 困难
给你一个整数数组 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
- class Solution {
- public:
- int numberOfArithmeticSlices(vector<int>& nums) {
-
- }
- };
和力扣873. 最长的斐波那契子序列的长度、力扣1027. 最长等差数列类似,动态规划解法思路:
状态表示:以某个位置为结尾,结合题目要求,先定义一个状态表示:
dp[i] 表示:以 i 位置元素为结尾的所有子序列中,等差数列的个数。
但是这里有⼀个非常致命的问题,那就是我们无法确定 i 结尾的斐波那契序列的样子。这样就会导致我们无法推导状态转移方程,因此我们定义的状态表示需要能够确定一个等差数列
根据等差数列的特性,我们仅需知道序列里面的最后两个元素,就可以确定这个序列的样子。因此,修改状态表示为:
dp[i][j] 表示:以 i 位置以及 j 位置的元素为结尾的所有的子序列中,等差数列的个数。规定一下 i < j 。
状态转移方程:
设 nums[i] = b, nums[j] = c ,那么这个序列的前一个元素就是 a = 2 * b - c。根据 a 的情况讨论:
综上,状态转移方程分情况讨论即可。
优化点:我们发现,在状态转移方程中,我们需要确定 a 元素的下标。因此我们可以在 dp 之前,将所有的元素和下标数组绑定在⼀起,放到哈希表中。这里为什么保存下标数组,是因为要统计个数,所有的下标都需要统计。
初始化:可以将表里面的值都初始化为 0 。
填表顺序:先固定斐波那契子序列的最后一个数,然后枚举倒数第二个数。
返回值:返回 dp 表中的所有值的和。
- class Solution {
- public:
- int numberOfArithmeticSlices(vector<int>& nums) {
- int n = nums.size(), ret = 0;
- vector<vector<int>> dp(n, vector<int>(n, 0));
- // dp[i][j] 表示:以 i 位置以及 j 位置的元素为结尾的所有的子序列中,等差数列的个数。i < j
- unordered_map<long long, vector<int>> hash(n);
- for(int i = 0; i < n; ++i)
- {
- hash[nums[i]].push_back(i);
- }
- for(int j = 2; j < n; ++j) // 固定倒数第一个数
- {
- for(int i = 1; i < j; ++i) // 先固定倒数第二个数
- {
- long long a = (long long)2 * nums[i] - nums[j]; // 防溢出
- if(hash.count(a))
- {
- for(auto& k : hash[a])
- {
- if(k < i)
- dp[i][j] += dp[k][i] + 1;
- else
- break;
- }
- }
- ret += dp[i][j];
- }
- }
- return ret;
- }
- };
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。