题目简述:
给你一个整数数组
nums,返回nums中最长等差子序列的长度。回想一下,
nums的子序列是一个列表nums[i1], nums[i2], ..., nums[ik],且0 <= i1 < i2 < ... < ik <= nums.length - 1。并且如果seq[i+1] - seq[i](0 <= i < seq.length - 1) 的值都相同,那么序列seq是等差的。提示:
2 <= nums.length <= 10000 <= nums[i] <= 500
题目链接:1027. 最长等差数列
看到这个数据范围,我就想到很可能需要设计一种 0 <= nums[i] <= 500,因此想到值域 DP。当然,如果未限定值域,也可以用哈希表处理。
因为一个等差数列的下一个元素的值是什么完全由上一个元素的值与数列的公差惟一决定,因此我想到设
-
如果
$d$ 使得该等差数列可能存在上一个元素,即$num-d$ 在0 <= nums[i] <= 500范围内,则 $$ dp[num][d]=\max\big{dp[num][d],\ dp[num-d][d]+1\big} $$ 由此我们也可以推导此时$d$ 的取值范围应满足$0\leqslant num-d\leqslant500$ ,即$d\geqslant num-500$ 且$d\leqslant num$ ; -
对于其他情况,由于
$d$ 实际上可以取到$[-500,500]$ 内的任何值,只不过当$d$ 不满足$0\leqslant num-d\leqslant500$ 时以$num$ 为最后一个元素、以$d$ 为公差的等差数列不存在上一个元素,所以这种情况下令$dp[num][d]=1$ 即可,表示$num$ 是该数列的首个元素。注意这里不能取
dp[num][d]++,否则会造成重复计数,因为无论数组的前面还存着几个值为$num$ 的元素,这时以$num$ 为最后一个元素、以$d$ 为公差的等差数列至多只能包含一个元素,例如$num=-450$ 、$d=100$,上一个元素的值$num-d=-550$ 已经超出了数组内元素的取值范围,这是不可能的。这种情况下必须记$dp[num][d]=1$ ,无论更新前$dp[num][d]$ 是$0$ 还是已经是$1$ 。
算法的时间复杂度为
class Solution {
public int longestArithSeqLength(int[] nums) {
int n = nums.length;
int[][] dp = new int[500 + 1][1000 + 1];
int res = 0;
for (int num : nums) {
for (int d = -500; d <= 500; d++) {
if (d >= num - 500 && d <= num) {
dp[num][d + 500] = Math.max(dp[num][d + 500], dp[num - d][d + 500] + 1);
} else {
dp[num][d + 500] = 1;
}
res = Math.max(res, dp[num][d + 500]);
}
}
return res;
}
}