33.最长等差数列(medium)

1.题目链接:

1027. 最长等差数列 - 力扣(LeetCode)1027. 最长等差数列 - 给你一个整数数组 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 是等差的。 示例 1:输入:nums = [3,6,9,12]输出:4解释: 整个数组是公差为 3 的等差数列。示例 2:输入:nums = [9,4,7,2,10]输出:3解释:最长的等差子序列是 [4,7,10]。示例 3:输入:nums = [20,1,15,3,10,5,8]输出:4解释:最长的等差子序列是 [20,15,10,5]。 提示: * 2 <= nums.length <= 1000 * 0 <= nums[i] <= 500https://leetcode.cn/problems/longest-arithmetic-subsequence/description/

2.题目描述:

给你一个整数数组 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 是等差的。​

示例 1:​
输入:nums = [3,6,9,12]​
输出:4​
解释: 整个数组是公差为 3 的等差数列。​
示例 2:​
输入:nums = [9,4,7,2,10]​
输出:3​

解释:最长的等差子序列是 [4,7,10]。​
示例 3:​
输入:nums = [20,1,15,3,10,5,8]​
输出:4​
解释:最长的等差子序列是 [20,15,10,5]。​

     提示:
             2 <= nums.length <= 1000​
             0 <= nums[i] <= 500​
3. 解法(动态规划):
算法思路:
1. 状态表示:
        对于线性 dp ,我们可以用「经验 + 题目要求」来定义状态表示:​

i.

以某个位置为结尾,巴拉巴拉;

ii. 以某个位置为起点,巴拉巴拉。

这里我们选择比较常用的方式,以某个位置为结尾,结合题目要求,定义一个状态表示:dp[i]  表示:以 i  位置元素为结尾的「所有子序列」中,最长的等差序列的长度。​
但是这里有一个非常致命的问题,那就是我们无法确定 i  结尾的等差序列的样子。这样就会导致我们无法推导状态转移方程,因此我们定义的状态表示需要能够确定一个等差序列。

根据等差序列的特性,我们仅需知道序列里面的最后两个元素,就可以确定这个序列的样子。因此,我们修改我们的状态表示为:
dp[i][j]  表示:以 i  位置以及 j  位置的元素为结尾的所有的子序列中,最长的等差序列的长度。规定一下 i < j 。​

2. 状态转移方程:
nums[i] = b, nums[j] = c ,那么这个序列的前一个元素就是 a = 2 * b - c 。我们根据 a  的情况讨论:​

a.

a  存在,下标为 k ,并且 a < b :此时我们需要以 k  位置以及 i  位置元素为结尾的最

长等差序列的长度,然后再加上 j  位置的元素即可。于是 dp[i][j] = dp[k][i] + 1 。这里因为会有许多个 k ,我们仅需离 i  最近的 k  即可。因此任何最长的都可以以 k为结尾;

b.

a  存在,但是 b < a < c :此时只能两个元素自己玩了,dp[i][j] = 2 ;​

c.

a  不存在:此时依旧只能两个元素自己玩了,dp[i][j] = 2 。​

综上,状态转移方程分情况讨论即可。

优化点:我们发现,在状态转移方程中,我们需要确定 a  元素的下标。因此我们可以将所有的元素 + 下标绑定在一起,放到哈希表中,这里有两种策略:

a.dp  之前,放入哈希表中。这是可以的,但是需要将下标形成一个数组放进哈希表中。这样  时间复杂度较高,我帮大家试过了,超时。

b. 一边 dp ,一边保存。这种方式,我们仅需保存最近的元素的下标,不用保存下标数组。但是用这种方法的话,我们在遍历顺序那里,先固定倒数第二个数,再遍历倒数第一个数。这样就可以在 i  使用完时候,将 nums[i]  扔到哈希表中。​

3. 初始化:

根据实际情况,可以将所有位置初始化为 2 。​

4. 填表顺序:

a. 先固定倒数第二个数;

b. 然后枚举倒数第一个数。​

5. 返回值:

由于不知道最长的结尾在哪里,因此返回 dp  表中的最大值。​

Java算法代码:

class Solution {
    public int longestArithSeqLength(int[] nums) {
        Map<Integer, Integer> hash = new HashMap<Integer, Integer>();
        hash.put(nums[0], 0);
        int  n = nums.length;
        int[][] dp = new int[n][n];
        for( int i = 0; i < n; i++){
            Arrays.fill(dp[i], 2);
        }
        int ret = 2;
        for(int i = 1; i < n; i++){
            for(int j = i + 1; j < n; j++){
                int a = 2 * nums[i] - nums[j];
                if(hash.containsKey(a)){
                    dp[i][j] = dp[hash.get(a)][i] + 1;
                    ret = Math.max(ret, dp[i][j]);
                }
            }
            hash.put(nums[i], i);
        }
        return ret;
    }
}

运行结果:

动态规划:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值