给你一个整数数组 nums。
如果子数组中相邻元素的差值是一个常数,那么这个子数组被称为 等差子数组。
你可以将 nums 中的 最多 一个元素替换为任意一个 整数。然后,从 nums 中选择一个等差子数组。
返回一个整数,该整数表示你可以选择的 最长 等差子数组的长度。
子数组 是数组中一段连续的元素序列。
示例 1:
输入: nums = [9,7,5,10,1]
输出: 5
解释:
将 nums[3] = 10 替换为 3,数组变为 [9, 7, 5, 3, 1]。
选择子数组 [9, 7, 5, 3, 1],它是等差数组,相邻元素的公差为 -2。
示例 2:
输入: nums = [1,2,6,7]
输出: 3
解释:
将 nums[0] = 1 替换为 -2,数组变为 [-2, 2, 6, 7]。
选择子数组 [-2, 2, 6, 7],它是等差数组,相邻元素的公差为 4。
提示:
4 <= nums.length <= 105
1 <= nums[i] <= 105
前后缀分解
- 可以在O(n)复杂度内计算出最长的等差长度
- 然后枚举每一个元素是否需要更改,更改了以后对前后序列的影响
- 一定要明白定义数组的含义,这题解法有好几个写法,原理不难,但是不同解法对应的下标不同,容易绕晕,我定义
pre[i]表示以nums[i]为结尾的等差序列的最大长度,这是从前往后 算的;suf[i]表示以nums[i]结尾的等差序列的长度,是从后往前算的
java
class Solution {
public int longestArithmetic(int[] nums) {
int n = nums.length;
// pre[i] 表示 nums[0..i] 中最长等差数列的长度
int[] pre = new int[n];
pre[0] = 1;
pre[1] = 2;
for (int i = 2; i < n; i++) {
if (nums[i] - nums[i-1] == nums[i - 1] - nums[i - 2]) {
pre[i] = pre[i - 1] + 1;
} else {
pre[i] = 2;
}
}
// suf[i]表示 nums[i..n-1] 中最长等差数列的长度
int[] suf = new int[n];
suf[n-1] = 1;
suf[n-2] = 2;
for (int i = n-3; i >= 0; i --) {
if (nums[i] - nums[i+1] == nums[i + 1] - nums[i + 2]) {
suf[i] = suf[i + 1] + 1;
} else {
suf[i] = 2;
}
}
int ans = 0;
for (int x : pre) {
ans = Math.max(ans, x);
}
// 这里ans + 1为了处理更改的元素为端点
ans ++;
if (ans >= n) { // 整个数组是等差的,或者修改端点元素后是等差的
return n;
}
for (int i = 1; i < n - 1; i++) {
// 把 nums[i] 改成 d2 / 2
int d2 = nums[i + 1] - nums[i - 1];
if (d2 % 2 != 0) { // d2 / 2 必须是整数
continue;
}
// 如果修改过的元素与之前的元素是等差的
boolean okLeft = i > 1 && nums[i - 1] - nums[i - 2] == d2 / 2;
boolean okRight = i + 2 < n && nums[i + 2] - nums[i + 1] == d2 / 2;
// suf[i+1]表示 nums[i+1..n-1] 中最长等差数列的长度
if (okLeft && okRight) {
ans = Math.max(ans, pre[i - 1] + 1 + suf[i + 1]);
} else if (okLeft) {
// +2 表示修改后的序列多加了nums[i].nums[i+1]
ans = Math.max(ans, pre[i - 1] + 2);
} else if (okRight) {
ans = Math.max(ans, suf[i + 1] + 2);
}
}
return ans;
}
}