加载中...
1218-最长定差子序列(Longest Arithmetic Subsequence of Given Difference)
发表于:2021-12-03 | 分类: 中等
字数统计: 364 | 阅读时长: 1分钟 | 阅读量:

原文链接: https://leetcode-cn.com/problems/longest-arithmetic-subsequence-of-given-difference

英文原文

Given an integer array arr and an integer difference, return the length of the longest subsequence in arr which is an arithmetic sequence such that the difference between adjacent elements in the subsequence equals difference.

A subsequence is a sequence that can be derived from arr by deleting some or no elements without changing the order of the remaining elements.

 

Example 1:

Input: arr = [1,2,3,4], difference = 1
Output: 4
Explanation: The longest arithmetic subsequence is [1,2,3,4].

Example 2:

Input: arr = [1,3,5,7], difference = 1
Output: 1
Explanation: The longest arithmetic subsequence is any single element.

Example 3:

Input: arr = [1,5,7,8,5,3,4,2,1], difference = -2
Output: 4
Explanation: The longest arithmetic subsequence is [7,5,3,1].

 

Constraints:

  • 1 <= arr.length <= 105
  • -104 <= arr[i], difference <= 104

中文题目

给你一个整数数组 arr 和一个整数 difference,请你找出并返回 arr 中最长等差子序列的长度,该子序列中相邻元素之间的差等于 difference

子序列 是指在不改变其余元素顺序的情况下,通过删除一些元素或不删除任何元素而从 arr 派生出来的序列。

 

示例 1:

输入:arr = [1,2,3,4], difference = 1
输出:4
解释:最长的等差子序列是 [1,2,3,4]。

示例 2:

输入:arr = [1,3,5,7], difference = 1
输出:1
解释:最长的等差子序列是任意单个元素。

示例 3:

输入:arr = [1,5,7,8,5,3,4,2,1], difference = -2
输出:4
解释:最长的等差子序列是 [7,5,3,1]。

 

提示:

  • 1 <= arr.length <= 105
  • -104 <= arr[i], difference <= 104

通过代码

高赞题解

序列 DP + 哈希表

定义 $f[i][j]$($j$ 非 $0$ 即 $1$) 为代表考虑前 $i$ 个数,且第 $i$ 个数的选择情况为 $j$ 时,得到的最长定差子序列长度。

最终答案为 $\max(f[n - 1][0], f[n - 1][1])$,同时我们有显然的初始化条件 $f[0][0] = 0$ 和 $f[0][1] = 1$。

不失一般性考虑 $f[i][j]$ 如何转移:

  • $f[i][0]$:明确了第 $i$ 个不选,那么此时最大长度为前一个位置的结果。即有:

$$
f[i][0] = \max(f[i - 1][0], f[i - 1][1])
$$

  • $f[i][1]$:明确了第 $i$ 个要选,此时进行分情况讨论:

    • $arr[i]$ 独立成为一个子序列,此时有:$f[i][1] = 1$;
    • $arr[i]$ 接在某一个数的后面,由于给定了差值 $difference$,可直接算得上一位的值为 $prev = arr[i] - difference$,此时应当找到值为 $prev$,下标最大(下标小于 $i$)的位置,然后从该位置转移过来,即有:$f[i][1] = f[hash[prev]][1] + 1$;

    容易证明:如果存在多个位置的值为 $prev$,从中选择一个下标最大的位置(下标小于 $i$)进行转移,结果相比于最优位置不会变差。因此我们「贪心」选择下标最大的位置(下标小于 $i$)即可,这引导我们在转移过程中使用「哈希表」记录处理过的位置的值信息。

    综上,我们有:

$$
f[i][1] = \begin{cases}
1 & hash[arr[i] - difference] = -1 \
f[hash[prev]][1] + 1 & hash[arr[i] - difference] \neq -1
\end{cases}
$$

image.png

代码(使用数组充当哈希表的代码在 $P2$):

[]
class Solution { public int longestSubsequence(int[] arr, int d) { int n = arr.length; Map<Integer, Integer> map = new HashMap<>(); int[][] f = new int[n][2]; f[0][1] = 1; map.put(arr[0], 0); for (int i = 1; i < n; i++) { f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]); f[i][1] = 1; int prev = arr[i] - d; if (map.containsKey(prev)) f[i][1] = Math.max(f[i][1], f[map.get(prev)][1] + 1); map.put(arr[i], i); } return Math.max(f[n - 1][0], f[n - 1][1]); } }
[]
class Solution { int N = 40009, M = N / 2; public int longestSubsequence(int[] arr, int d) { int n = arr.length; int[] hash = new int[N]; Arrays.fill(hash, -1); int[][] f = new int[n][2]; f[0][1] = 1; hash[arr[0] + M] = 0; for (int i = 1; i < n; i++) { f[i][0] = Math.max(f[i - 1][0], f[i - 1][1]); f[i][1] = 1; int prev = arr[i] - d; if (hash[prev + M] != -1) f[i][1] = Math.max(f[i][1], f[hash[prev + M]][1] + 1); hash[arr[i] + M] = i; } return Math.max(f[n - 1][0], f[n - 1][1]); } }
  • 时间复杂度:令 $n$ 为数组长度,共有 $n * 2$ 个状态需要被计算,每个状态转移的复杂度为 $O(1)$。整体复杂度为 $O(n)$
  • 空间复杂度:$O(n)$

优化状态定义

不难发现,我们多定义一维状态来区分某个位置的值是否被选择,目的是为了正确转移出第 $i$ 位被选择的情况。

事实上,利用哈希表本身我们就能轻松做到这一点。

我们调整状态定义为:**$f[i]$ 为考虑前 $i$ 个数(第 $i$ 个数必选)时,得到的最长定差子序列长度。**

不失一般性考虑 $f[i]$ 该如何转移,分情况讨论:

  • $arr[i]$ 独立成为一个子序列,此时有:$f[i] = 1$;
  • $arr[i]$ 接在某一个数的后面,由于给定了差值 $difference$,可直接算得上一位的值为 $prev = arr[i] - difference$,此时应当找到 $arr[j]$ 为 $prev$ 的最新位置(下标最大,同时满足 $j < i$)当时的转移结果,在此基础上加一即可,即有:$f[i] = hash[prev] + 1$;

综上,我们有($hash$ 初始化为 $0$):

$$
f[i] = hash[prev] + 1
$$

image.png

代码(使用数组充当哈希表的代码在 $P2$):

[]
class Solution { public int longestSubsequence(int[] arr, int d) { int ans = 1; Map<Integer, Integer> map = new HashMap<>(); for (int i : arr) { map.put(i, map.getOrDefault(i - d, 0) + 1); ans = Math.max(ans, map.get(i)); } return ans; } }
[]
class Solution { int N = 40009, M = N / 2; public int longestSubsequence(int[] arr, int d) { int ans = 1; int[] hash = new int[N]; for (int i : arr) { hash[i + M] = hash[i - d + M] + 1; ans = Math.max(ans, hash[i + M]); } return ans; } }
  • 时间复杂度:令 $n$ 为数组长度,共有 $n$ 个状态需要被计算,每个状态转移的复杂度为 $O(1)$。整体复杂度为 $O(n)$
  • 空间复杂度:$O(n)$

其他

意犹未尽?考虑加练如下「序列 DP」内容 🍭🍭🍭

题目 题解 难度 推荐指数
354. 俄罗斯套娃信封问题 LeetCode 题解链接 困难 🤩🤩🤩🤩🤩
368. 最大整除子集 LeetCode 题解链接 中等 🤩🤩🤩🤩
446. 等差数列划分 II - 子序列 LeetCode 题解链接 困难 🤩🤩🤩🤩🤩
583. 两个字符串的删除操作 LeetCode 题解链接 中等 🤩🤩🤩🤩
673. 最长递增子序列的个数 LeetCode 题解链接 中等 🤩🤩🤩🤩
740. 删除并获得点数 LeetCode 题解链接 中等 🤩🤩🤩🤩🤩
978. 最长湍流子数组 LeetCode 题解链接 中等 🤩🤩🤩
1035. 不相交的线 LeetCode 题解链接 中等 🤩🤩🤩🤩
1143. 最长公共子序列 LeetCode 题解链接 中等 🤩🤩🤩🤩
1473. 粉刷房子 III LeetCode 题解链接 困难 🤩🤩🤩🤩
1713. 得到子序列的最少操作次数 LeetCode 题解链接 困难 🤩🤩🤩🤩🤩

注:以上目录整理来自 wiki,任何形式的转载引用请保留出处。


最后

如果有帮助到你,请给题解点个赞和收藏,让更多的人看到 ~ (“▔□▔)/

也欢迎你 关注我(公主号后台回复「送书」即可参与长期看题解学算法送实体书活动)或 加入「组队打卡」小群 ,提供写「证明」&「思路」的高质量题解。

所有题解已经加入 刷题指南,欢迎 star 哦 ~

统计信息

通过次数 提交次数 AC比率
36706 71394 51.4%

提交历史

提交时间 提交结果 执行时间 内存消耗 语言
上一篇:
1217-玩筹码(Minimum Cost to Move Chips to The Same Position)
下一篇:
1219-黄金矿工(Path with Maximum Gold)
本文目录
本文目录