开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第10天,点击查看活动详情。
Given an array A of integers, return the length of the longest arithmetic subsequence in A. Recall that a subsequence of A is a list A[i_1], A[i_2], ..., A[i_k] with 0 <= i_1 < i_2 < ... < i_k <= A.length - 1, and that a sequence B is arithmetic if B[i+1] - B[i] are all the same value (for 0 <= i < B.length - 1).
Example 1: Input: [3,6,9,12] Output: 4 Explanation: The whole array is an arithmetic sequence with steps of length = 3.
Example 2: Input: [9,4,7,2,10] Output: 3 Explanation: The longest arithmetic subsequence is [4,7,10].
Example 3: Input: [20,1,15,3,10,5,8] Output: 4 Explanation: The longest arithmetic subsequence is [20,15,10,5].
Note: 1. 2 <= A.length <= 2000 2. 0 <= A[i] <= 10000
- Approach 1: DP (Based on Array)
- 这道问题与 LIS 具有一定的类似度。只不过这里要求的时等差数列。
- 因此我们想到使用 DP 来解决这个问题。
- 原因为:当结尾数字 A[i] 和 差值diff 一旦确定之后,答案变已经确定了下来,与如何到达当前状态无关。
- 对此我们可以使用到 dp[][] 来表示对应的状态:
- dp[i][diff]: 表示以 A[i] 作为等差数列结尾,差值为 diff 的最长等差数列的长度。
- 则,我们可以得到状态转移方程:
- dp[j][diff] = Math.max(dp[j][diff], dp[i][diff] + 1)
- 因为 diff 可能为负数,所以这里需要将其 +offset 使其全都转换成 正整数 进行处理。(题目中已经说明所有A[i]为非负数)
- 这个技巧在使用 arr[] 替代 Map 中非常常见。
- 初始状态时,各个值均为 1. (只有一个数本身,因此初始长度为1)
- 但是值得注意的是,Java无法在开辟空间的时候,对数组值进行初始化,这点与 C++ 和 Python 不同。
- 因此,为了省去初始化所需要 O(N*M) 的时间,这里使用了原本的初始值 0.
- 而在最后对返回结果 ans 进行了 +1 操作,从而得到我们需要的结果。
- (这里是否进行初始化大家可以根据自身的编码习惯进行取舍,注意对 ans 的处理即可)
- 时间复杂度:O(N^2)
- 空间复杂度:O(N*M)
class Solution {
public int longestArithSeqLength(int[] A) {
int offset = Integer.MIN_VALUE;
for (int num : A) {
offset = Math.max(offset, num);
}
/*int ans = 2;
int[][] dp = new int[A.length][2 * offset + 1];
for (int[] arr : dp) {
Arrays.fill(arr, 1);
}*/
int ans = 1;
int[][] dp = new int[A.length][2 * offset + 1];
for (int i = 0; i < A.length - 1; i++) {
for (int j = i + 1; j < A.length; j++) {
int diff = A[j] - A[i] + offset; // 保证 diff 为非负数
dp[j][diff] = Math.max(dp[j][diff], dp[i][diff] + 1);
ans = Math.max(ans, dp[j][diff]);
}
}
// return ans;
return ans + 1;
}
}
- Approach 2: DP (Based on HashMap)
- 解法思路说 Approach 1 一模一样,但是说实话,个人并不喜欢这个解法...
- 虽然引入了 Map,但是并不利于对数据含义进行表达,反而没有使用数组来得直观,
- 并且使用了 HashMap 和 lambda 表达式使得运行效率低了许多。
- 好处就是看似省去了一些不必要的空间(diff)和易于存储 递减关系 的序列罢了。
- 时间复杂度:O(N^2)
- 空间复杂度:O(N*M)
class Solution {
public int longestArithSeqLength(int[] A) {
// dp的键值对代表含义:key -> 等差数列的数值之差; value -> map
// map的键值对代表含义:key -> 以 A[i] 作为结尾的等差数列; value -> 该等差数列最长的长度
Map<Integer, Map<Integer, Integer>> dp = new HashMap<>();
int ans = 2;
for (int i = 0; i < A.length - 1; i++) {
for (int j = i + 1; j < A.length; j++) {
Map<Integer, Integer> map = dp.computeIfAbsent(A[j] - A[i], diff -> new HashMap<>());
map.put(j, map.getOrDefault(i, 1) + 1);
ans = Math.max(ans, map.get(j));
}
}
return ans;
}
}