【LeetCode】413. Arithmetic Slices

问题描述

问题链接:https://leetcode.com/problems/arithmetic-slices/#/description

A sequence of number 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 sequence:

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 slice of that array is any pair of integers (P, Q) such that 0 <= P < Q < N.

A slice (P, Q) of array A is called arithmetic if the sequence:
A[P], A[p + 1], …, A[Q - 1], A[Q] is arithmetic. In particular, this means that P + 1 < Q.

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

Example:

A = [1, 2, 3, 4]

return: 3, for 3 arithmetic slices in A: [1, 2, 3], [2, 3, 4] and [1, 2, 3, 4] itself.

我的代码

思路探索

随手试了一下,发现分趟扫描法可以解决这个问题。

1,3,5,4,3,7,11

1,3,5
    5,4,3
        3,7,11

主要思路是每趟扫描只查找指定长度的arithmetic,这样就容易多了。

通过代码

我通过的代码如下:

public class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        /*
        整体的思路是一趟趟的扫描,第1趟扫描长度是3的arithmetic
        第2趟扫描长度是4的,第3趟扫描长度是5的。。。直到扫描整个A
        每趟扫描的时候使用一些辅助变量记录当前已经连续几个数符合等差,d是多少,目前的扫描位置等
        如果连续长度达到本趟的要求,计数器加1,
        扫描位置后移,连续长度--,如果下一个数与当前位置差值不是d,长度变为2,d更新为新差值
        基本就这样了。
        */
        int len = A.length;
        if(len < 3){
            return 0;
        }

        int count = 0;

        for(int curLen = 3; curLen <= len; curLen ++){
            count += scan(A,curLen);
        }
        return count;
    }

    private int scan(int[] A, int curLen){
        int len = A.length;
        int d = -9999;   // 等差数列的公差,初始值一定要避免出现在等差数列的公差里面
        int aLen = 0; // 等差数列的长度

        int count = 0; // arithmetic的数量

        for(int i = 1; i < len; i++){ // 从1开始是为了后面直接计算A[i]和A[i - 1]的差值
            if(A[i] - A[i - 1] == d){ // 新差值与公差相同
                aLen ++; // 数列长度递增
                if(aLen >= curLen){
                    // 长度符合要求,发现一个arithmetic
                    count++;
                    continue;
                }
            }else{                    // 新差值与公差相同,把A[i]和A[i - 1]作为新的等差数列
                aLen = 2;
                d = A[i] - A[i - 1];
            }
        }
        return count;
    }
}

只打败了1.96%的Java代码,而大家代码的分布情况是这样的:

所以我料定还有更好的方法。

代码优化

看起来,我的代码主要的耗时就在于一趟趟的扫描,而一趟扫描只能找固定长度的arithmetic。能不能一趟扫描就把数量都求出来呢?我觉得是可以的,因为我每趟扫描的时候都拿到了aLen,而count却只加了1。但是我们知道不同的aLen可以得到不同数量的arithmetic。比如aLen = 3,可以找到1个,aLen = 4,可以找到3个。aLen = 5,可以找到6个,aLen = 6可以找到10个。因此,每次aLen++能够算出由此多出的count数量。

优化后的代码如下:

public class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        /*
        整体的思路是一趟趟的扫描,第1趟扫描长度是3的arithmetic
        第2趟扫描长度是4的,第3趟扫描长度是5的。。。直到扫描整个A
        每趟扫描的时候使用一些辅助变量记录当前已经连续几个数符合等差,d是多少,目前的扫描位置等
        如果连续长度达到本趟的要求,计数器加1,
        扫描位置后移,连续长度--,如果下一个数与当前位置差值不是d,长度变为2,d更新为新差值
        基本就这样了。
        */
        int len = A.length;
        if(len < 3){
            return 0;
        }

        int count = scan(A);

        return count;
    }

    private int scan(int[] A){
        int len = A.length;
        int d = -9999;   // 等差数列的公差,初始值一定要避免出现在等差数列的公差里面
        int aLen = 0; // 等差数列的长度

        int count = 0; // arithmetic的数量

        for(int i = 1; i < len; i++){ // 从1开始是为了后面直接计算A[i]和A[i - 1]的差值
            if(A[i] - A[i - 1] == d){ // 新差值与公差相同
                aLen ++; // 数列长度递增
                count += (calArithmetics(aLen) - calArithmetics(aLen - 1));
            }else{                    // 新差值与公差相同,把A[i]和A[i - 1]作为新的等差数列
                aLen = 2;
                d = A[i] - A[i - 1];
            }
        }
        return count;
    }

    private int calArithmetics(int aLen){
        if(aLen <= 2){
            return 0;
        }
        int n = aLen - 2;
        return (1 + n) * n / 2;
    }
}

这次打败了13.54%的代码,虽然提高的有限,但是已经进入第一集团了。如图所示:

来,再到讨论区跟大神们学习一个。

讨论区

Simple Java solution 9 lines, 2ms

链接地址:https://discuss.leetcode.com/topic/63302/simple-java-solution-9-lines-2ms

思路和我的基本一样,但人家写的好简洁啊。

public int numberOfArithmeticSlices(int[] A) {
    int curr = 0, sum = 0;
    for (int i=2; i<A.length; i++)
        if (A[i]-A[i-1] == A[i-1]-A[i-2]) {
            curr += 1; // 当前等差数列长度-2
            sum += curr; // 1 + 2 + 3
        } else {
            curr = 0;
        }
    return sum;
}

3ms C++ Standard DP Solution with Very Detailed Explanation

链接地址:https://discuss.leetcode.com/topic/62992/3ms-c-standard-dp-solution-with-very-detailed-explanation

这是一个动态规划的版本。一直很想学习动态规划,这个例子挺经典的,可以思考一下动态规划的思想。

class Solution {
public:
    int numberOfArithmeticSlices(vector<int>& A) {
        int n = A.size();
        if (n < 3) return 0;
        vector<int> dp(n, 0); // dp[i] means the number of arithmetic slices ending with A[i]
        if (A[2]-A[1] == A[1]-A[0]) dp[2] = 1; // if the first three numbers are arithmetic or not
        int result = dp[2];
        for (int i = 3; i < n; ++i) {
            // if A[i-2], A[i-1], A[i] are arithmetic, then the number of arithmetic slices ending with A[i] (dp[i])
            // equals to:
            //      the number of arithmetic slices ending with A[i-1] (dp[i-1], all these arithmetic slices appending A[i] are also arithmetic)
            //      +
            //      A[i-2], A[i-1], A[i] (a brand new arithmetic slice)
            // it is how dp[i] = dp[i-1] + 1 comes
            if (A[i]-A[i-1] == A[i-1]-A[i-2]) 
                dp[i] = dp[i-1] + 1;
            result += dp[i]; // accumulate all valid slices
        }
        return result;
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值