leetcode446:等差数列划分 II - 子序列

题目很难,我忍一下… CV大法好


leetcode446:等差数列划分 II - 子序列_第1张图片

题目

给你一个整数数组 nums ,返回 nums 中所有 等差子序列 的数目。

如果一个序列中 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该序列为等差序列。

例如,[1, 3, 5, 7, 9]、[7, 7, 7, 7] 和 [3, -1, -5, -9] 都是等差序列。
再例如,[1, 1, 2, 5, 7] 不是等差序列。

数组中的子序列是从数组中删除一些元素(也可能不删除)得到的一个序列。

例如,[2,5,10] 是 [1,2,1,2,4,1,5,10] 的一个子序列。

题目数据保证答案是一个 32-bit 整数。

示例 1

输入:nums = [2,4,6,8,10]
输出:7
解释:所有的等差子序列为:
[2,4,6]
[4,6,8]
[6,8,10]
[2,4,6,8]
[4,6,8,10]
[2,4,6,8,10]
[2,6,10]

示例 2

输入:nums = [7,7,7,7,7]
输出:16
解释:数组中的任意子序列都是等差子序列。

题干要求的是等差子序列,序列是不连续的。
我们可以使用两个for循环,第一个for循环固定等差子序列的最后一项,第2个for循环固定等差子序列的倒数第二项(枚举等差子序列的倒数第2项),类似于[……,nums[j],nums[i]],最后两项固定了,那么他们的等差值肯定是确定了,然后在根据等差值往前查找。代码如下

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {

        //等差子序列的个数
        int count = 0;
        int length = nums.length;
        //map数组,其中map[i]中key和value分别是以num[i]为
        //倒数第二项的等差子序列的等差值和等差子序列个数
        Map<Long, Integer>[] map = new Map[length];
        map[0] = new HashMap<>();
        //固定等差子序列的最后一项
        for (int i = 1; i < length; i++) {
            map[i] = new HashMap<>();
            //枚举等差子序列的倒数第2项
            for (int j = i - 1; j >= 0; j--) {
                //等差子序列的后两项,[……,nums[j],nums[i]]。
                // 由于等差值可能出现越界,先转换为long类型
                long diff = (long) nums[i] - nums[j];
                //在int范围内,等差值不能太大
                if (diff <= Integer.MIN_VALUE || diff >= Integer.MAX_VALUE)
                    continue;
                //等差子序列的最后两项固定了,那么他们的等差值也就确定了,前面的
                //我们只需要从map中查找即可

                //count_j是以nums[i]为等差子序列最后一项的等差子序列个数,nums[j]是
                //倒数第2项,类似于[……,nums[j],nums[i]]
                int count_j = map[j].getOrDefault(diff, 0);
                //统计所有以nums[i]为等差子序列最后一项的等差子序列个数
                count += count_j;
                //保存以nums[i]为倒数第二项的等差子序列个数
                map[i].put(diff, map[i].getOrDefault(diff, 0) + count_j + 1);
            }
        }
        return count;
    }
}

leetcode446:等差数列划分 II - 子序列_第2张图片注意:

上面代码有两个注意点

等差数列的长度至少是3,所以等差值不可能超过Integer.MAX_VALUE和小于Integer.MIN_VALUE,即使[Integer.MIN_VALUE,0,Integer.MAX_VALUE]也不可能,所以如果等差值超过这个范围,说明不可能构成等差数列,直接跳过。
map[i]中的key和value分别记录的是以num[i]为倒数第2项构成的等差数列的等差值和个数。这是因为这样做我们可以过滤掉只有两个元素的等差数列。

官方正解:动态规划 + 哈希表

我们首先考虑至少有两个元素的等差子序列,下文将其称作弱等差子序列。

由于尾项和公差可以确定一个等差数列,因此我们定义状态 f[i][d] 表示尾项为 nums[i],公差为 d 的弱等差子序列的个数。

我们用一个二重循环去遍历 nums 中的所有元素对 (nums[i],nums[j]),其中 j

f[i][d]+=f[j][d]+1

由于题目要统计的等差子序列至少有三个元素,我们回顾上述二重循环,其中「将 nums[i] 加到以 nums[j]为尾项,公差为 d 的弱等差子序列的末尾」这一操作,实际上就构成了一个至少有三个元素的等差子序列,因此我们将循环中的 f[j][d] 累加,即为答案。

代码实现时,由于 nums[i]的范围很大,所以计算出的公差的范围也很大,我们可以将状态转移数组 f的第二维用哈希表代替。

class Solution {
    public int numberOfArithmeticSlices(int[] nums) {
        int ans = 0;
        int n = nums.length;
        Map<Long, Integer>[] f = new Map[n];
        for (int i = 0; i < n; ++i) {
            f[i] = new HashMap<Long, Integer>();
        }
        for (int i = 0; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                long d = 1L * nums[i] - nums[j];
                int cnt = f[j].getOrDefault(d, 0);
                ans += cnt;
                f[i].put(d, f[i].getOrDefault(d, 0) + cnt + 1);
            }
        }
        return ans;
    }
}


作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/arithmetic-slices-ii-subsequence/solution/deng-chai-shu-lie-hua-fen-ii-zi-xu-lie-b-77pl/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

好难好难好难好难好难好难好难好难好难好难好难

你可能感兴趣的:(leetcode,java,数据结构,算法,leetcode,动态规划)