1819-序列中不同最大公约数的数目

写在前面

这次周赛的第四题还是比较有意思的,尤其是时间复杂度方面,给的数据范围在10^5,需要O(NlogN)的算法,就很容易将思想局限在二分、排序、堆、并查集,这些方法之中,而这道题复杂度的计算用到级数的概念,表面接近O(N ^ 2)的算法,实际复杂度为O(NlogN),还是十分值得思考的一道题目。

题目

核心思路

这道题目使用暴力的解法显然是不行的,单单是枚举所有子序列都需要指数级别的时间复杂度,而题目需要的算法为O(NlogN),显然会超时。不过注意到给定数组中元素的大小范围限定在[1, 200000],给了这一限制,显然是一个突破口。
所以不如直接枚举 元素的值i ∈ [1, 200000],为了得到结果,就需要判断当前元素i是否能作为某一个子序列的最大公约数。如果可以的话,这个子序列中的元素必然都能被i整除,例如:i, 2i, 4i, 5i, 9i,那么就可以枚举i, 2i, 3i, 4i, ...并判断其是否在数组中,然后将其加入到一个可能的子序列里,并求得这个子序列的最大公约数curGcd,如果curGcd == i的话,就代表枚举到的i是满足的,将答案加一即可。
这里需要承认的一个事实是:如果存在一个子序列a, b, c的最大公约数为i,那么新加入一个元素x,整个序列a, b, c, x的最大公约数应该为gcd(i, x)
有了这个事实,在求子序列的最大公约数时就可以每添加一个元素求解一次curGcd,然后判断是否为目标值i即可,不需要维护这个枚举的子序列了。

完整代码

class Solution {

    public int gcd(int a, int b){
        return a % b == 0 ? b : gcd(b, a % b);
    }

    public int countDifferentSubsequenceGCDs(int[] nums) {
        boolean[] mark = new boolean[200001];
        int maxNum = 0;
        for(int num : nums) mark[num] = true;

        int res = 0;
        for(int i = 1; i < 200001; i++){
            int curGcd = 0;
            for(int j = i; j < 200001; j += i){
                if(mark[j]){
                    if(curGcd == 0) curGcd = j;
                    else curGcd = gcd(curGcd, j);
                    if(curGcd == i){
                        res++;
                        break;
                    }
                }
            }
        }
        return res;
    }
}

代码也不难,就是根据上边的思路维护一个结果res和当前子序列的最大公约数curGcd即可。比较难理解的就是算法的时间复杂度的计算了,看似时间为O(N ^ 2),不过实际的复杂度应该为O(NlogN),这里的N为数组元素的最大值,代码为了简洁取了数据范围峰值,具体计算方法如下:


通过上述公式计算,总的时间复杂度应该为O(m * logm),m代表数组元素的最大值,是满足题目要求的。

你可能感兴趣的:(1819-序列中不同最大公约数的数目)