413. 等差数列划分
问题描述
如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
- 例如,
[1,3,5,7,9]
、[7,7,7,7]
和[3,-1,-5,-9]
都是等差数列。
给你一个整数数组 nums
,返回数组 nums
中所有为等差数组的 子数组 个数。
子数组 是数组中的一个连续序列。
示例 1:
输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。
示例 2:
输入:nums = [1]
输出:0
提示:
1 <= nums.length <= 5000
-1000 <= nums[i] <= 1000
解题思路与代码实现
使用滑动窗口+动态规划:
滑动窗口:求给定数组是否存在长度大于等于3的等差数列
动态规划:求解长度为n(n>=3)的等差数列所拥有的子数组的个数;
边界条件:dp[1] = 0, dp[2] = 3
状态转移方程: dp[i] = dp[i-1] + i-2
class Solution {
/**
* 如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。
* 利用滑动窗口 找到数组中的等差数列,并将它们的长度保存到list返回
*
* @param nums
* @return
*/
private List<Integer> partion(int[] nums) {
int left = 0, right = 1;
int d = nums[1] - nums[0];
List<Integer> result = new ArrayList<>();
while (left < nums.length && right < nums.length) {
// 如果当前窗口内的元素能组成等差数列
if (nums[right - 1] + d == nums[right]) {
right++; // 右边界扩张
// 右移后右边界如果越界,说明数组已经被遍历一遍了
if (right >= nums.length) {
// 判断当前窗口大小(right-left)是否大于等于3
if (right - left >= 3) {
result.add(right - left);
}
}
} else {
// 判断当前窗口大小(right-left)是否大于等于3
if (right - left >= 3) {
result.add(right - left);
}
// 左边界收缩
left = right - 1;
// 更新公差d
d = nums[right] - nums[left];
}
}
// 返回结果
return result;
}
public int numberOfArithmeticSlices(int[] nums) {
// 长度小于3的数组不会存在等差数列,直接返回0
if (nums.length < 3) {
return 0;
}
List<Integer> list = partion(nums);
// 如果list为空,说明数组中并不存在连续的等差数列,直接返回0
if (list.isEmpty()) {
return 0;
}
Integer max = list.stream().max(Comparator.naturalOrder()).get();
// max<= nums.length,节约部分空间
int[] dp = new int[max + 1];
// dp[i]表示长度为i的等差数列所包含的子数组 个数
for (int i = 1; i < dp.length; i++) {
if (i <= 2) {
// 边界条件
dp[i] = 0;
} else {
// 状态转移方程
dp[i] = dp[i - 1] + (i - 2);
}
}
// return dp[nums.length];
int result = 0;
for (Integer i : list) {
result += dp[i];
}
return result;
}
}
数学规律:在本题中,长度为n的等差数列会比长度为n-1的等差数列的子数组个数多n-2个(1…n, …, n-2…n),即有 f(n) = f(n-1) + n-2;而f(3) = 1, 据此可以算出长度为n(n>=3)的等差数列子数组个数:
f(n) = f(n-1) + n-2 = f(1) + 2 + 3 + … + n-2
= (1+n-2)*(n-2)/2
=(n^2-3n+2)/2
所以,简化numberOfArithmeticSlices代码
public int numberOfArithmeticSlices2(int[] nums) {
// 长度小于3的数组不会存在等差数列,直接返回0
if (nums.length < 3) {
return 0;
}
List<Integer> list = partion(nums);
// 如果list为空,说明数组中并不存在连续的等差数列,直接返回0
if (list.isEmpty()) {
return 0;
}
int result = 0;
for (Integer i : list) {
result += (i * i - 3 * i + 2) / 2;
}
return result;
}