[LeetCode] 446. Arithmetic Slices II - Subsequence

版权声明:如需转载请注明出处 https://blog.csdn.net/Bertram03/article/details/87300773

原题链接: https://leetcode.com/problems/arithmetic-slices-ii-subsequence/

相关题目:413. Arithmetic Slices

1. 题目介绍

A sequence of numbers is called arithmetic if it consists of at least three elements and if the difference between any two consecutive elements is the same.

For example, these are arithmetic sequences:

1, 3, 5, 7, 9
7, 7, 7, 7
3, -1, -5, -9
The following sequence is not arithmetic.

1, 1, 2, 5, 7

A zero-indexed array A consisting of N numbers is given. A subsequence slice of that array is any sequence of integers (P0, P1, …, Pk) such that 0 ≤ P0 < P1 < … < Pk < N.

A subsequence slice (P0, P1, …, Pk) of array A is called arithmetic if the sequence A[P0], A[P1], …, A[Pk-1], A[Pk] is arithmetic. In particular, this means that k ≥ 2.

The function should return the number of arithmetic subsequence slices in the array A.

The input contains N integers. Every integer is in the range of -231 and 231-1 and 0 ≤ N ≤ 1000. The output is guaranteed to be less than 231-1.

给出一个数组,找其中所有的等差数列。等差数列的要求是至少由3个数组成。
需要注意的是,可以跳过一些数字找。比如数列1 2 3 4 5 ,1 3 5也算是其中的一个等差数列。

Example:

Input: [2, 4, 6, 8, 10]
Output: 7

Explanation:
All arithmetic subsequence slices are:
[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. 解题思路

动态规划法

使用动态规划来解决本题。这部分内容采用了LeetCode上本题的 Solution的动态规划解法。

1. 设计二维数组

我们如何才能确定一个等差数列呢,那就是掌握这个数列的起始或者最终的元素,还有它的公差。

于是我们使用一个二维数组,dp[ i ][ d ] 存放等差数列的数量。dp[ i ][ d ] 里面存放的数,就是以A[ i ]结尾,公差是d的等差数列的个数。

2. 寻找状态转移方程

设计好二维数组后,我们就要去寻找状态转移方程了。

每当我们遇到一个元素A[ i ],当A[ i ]减去它前面的等差数列的最后一个数的值,等于这个等差数列的公差时,我们就可以将A[ i ]加入这个等差数列了。

所以,假设A[ j ]就是这个等差数列的最后一个数,并且j < i,那么状态转移方程可以这么写:
如果 A[i] - A[j] 等于公差,那么

dp[ i ][ A[i] - A[j] ] +=  dp[ j ][ A[i] - A[j] ]

但问题来了。 最初所有dp [ i ] [ d ]初值都是0,怎么加都是0呀。我们如何计算新形成的等差数列个数?

在本题对等差数列的定义中,数列的长度必须至少为3。如果仅给出两个索引i和j,则很难形成新的子序列。 如何考虑长度为2的子序列?

我们可以定义一个弱等差序列,它的至少由2个元素组成就可以了。它有两个属性:

  1. 任意2个数都可以形成弱等差数列,A[i] A[j]
  2. 遇到一个新元素,如果它等于弱等差数列的最后一个数加上公差的话,他们就可以形成新的等差数列。

我们把dp中存放的数改成是弱等差数列的数量,于是,新的状态转移方程是:
如果 A[i] - A[j] 等于公差,那么

dp[ i ][ A[i] - A[j] ] +=  dp[ j ][ A[i] - A[j] ] +1

dp[ i ][ d ] 里面存放的数,就是以A[ i ]结尾,公差是d的 等差数列的个数。
这里面加的1,就是 A[ j ] 、A[ i ]这对只有2个元素的弱等差数列。

如果把所有的dp[ i ][ d ]加起来,就是所有弱等差数列的个数之和了。
然后用这个和,减去只有2个元素的等差数列的个数,那就得到了全部长度至少为3的等差数列的个数了。
只有2个元素的等差数列,就是从数组中任取2个数,使用排列组合 Cn2 = n* (n-1)/2

计算 。然后用所有的dp[ i ][ d ]的和减去 n* (n-1)/2 就可以了。

3. 举例说明

以数组 [1, 1, 2, 3, 4, 5] 为例

A[0] = 1,在它之前没有数,更没有等差数列,所以全都是0
在这里插入图片描述
A[1] = 1,在它之前,无等差数列,但是有一个数可以和它构成公差为0的弱等差数列,因此加1。
dp[1][ 1 - 1 ] += dp[ 0 ][ 1-1 ] +1
dp[1][ 1 - 1 ] = 0 + 0 +1
在这里插入图片描述
A[2] = 2,A[2]和A[0]构成一个公差为1的等差数列,A[2]和A[1]构成一个公差为1的等差数列,共2个。所以dp[2][1]加了2个1,为2。
在这里插入图片描述
依次类推,可以得到整个dp数组:
在这里插入图片描述
dp数组中的数加起来为26,只有两个数的等差数列有6*5/2 = 15个, 26-15=11 即为正确答案。
而且有个更简单的办法可以直接得出大于3个数的等差数列的个数,那就是设置一个求和的值sum,每次只加上dp[ j ][ d ]而忽略那个1。

正如本题Solution的图所示:
在这里插入图片描述

4. 具体实现细节

但是由于本题的数据范围太大了,而且二维数组很多地方都是0,并没有真正用到。所以不可能真的用一个二维数组去实现,于是采用一个技巧:Map数组。

Map<Integer, Integer>[] map = new Map[length];

map[ i ]代表dp数组中的一维数组dp[ i ],
Map<Integer, Integer>,第一个Integer是公差,是key;第二个Integer是弱等差数列的个数,是value。
通过key可以找到唯一的value与其对应。完美的解决了二维数组开销过大的问题。

实现代码

public class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        
        int length = A.length;
        if (length == 0 || A == null) {
        	return 0;
        }
        
        int res = 0;
        Map<Integer, Integer>[] map = new Map[length];
        //这是定义了一个Map的数组

        for (int i = 0; i < length; i++) {
            map[i] = new HashMap<>();

            for (int j = 0; j < i; j++) {
                long diff = (long)A[i] - A[j];
                if (diff <= Integer.MIN_VALUE || diff > Integer.MAX_VALUE) continue;
                //测试样例中有超过int范围的公差,默认它无法构成int的等差数组
                
                int d = (int)diff;
            	
                int c1 = map[i].getOrDefault(d, 0);
                //dp[i][d]
                int c2 = map[j].getOrDefault(d, 0);
                //dp[j][d]
                /*
                 * getOrDefault(Object key, V defaultValue)
                 * 当Map集合中有d这个key时,就使用这个key值,如果没有就使用默认值0
                 */
                
                res += c2;
                map[i].put(d, c1 + c2 + 1);
                /* put方法
                 * 如果put相同的key,最后一个key对应的value
                 * 会把前一个相同key的value覆盖掉
                 */
            }
        }
        return res;
    }

}

3. 参考材料

https://leetcode.com/problems/arithmetic-slices-ii-subsequence/solution/

猜你喜欢

转载自blog.csdn.net/Bertram03/article/details/87300773