leetcode刷题记录day021:326和204

这篇博客探讨了多种求解质数的方法,包括试除法、最大3的幂的约数判断、埃拉托斯特尼筛法以及线性筛。详细介绍了每种方法的时间复杂度和空间复杂度,并提供了具体的Java实现。通过比较,强调了优化算法在处理大规模数据时的重要性。
摘要由CSDN通过智能技术生成
326、难度简单:

要求:不使用循环或者递归来完成本题
注意:3的0次 =1,这也需要被考虑在内。

方法一:试除法:时间复杂度:O(log n) 空间复杂度:O(1)

我们不断地将 n 除以 3,直到 n=1。如果此过程中 n 无法被 3 整除,就说明 n 不是 3 的幂。
本题中的 n 可以为负数或 0,可以直接提前判断该情况并返回 False,也可以进行试除,因为负数或 0 也无法通过多次除以 3 得到 1

class Solution {
    public boolean isPowerOfThree(int n) {
        while (n != 0 && n % 3 == 0) {
            n /= 3;
        }
        return n == 1;
    }
}

采取循环除 3 而非循环乘 3 原因:假设传入数据为 2147483647 ,如果采取乘 3 ,那么从 1 开始乘,将耗时巨大。
而采用除 3 ,当第一次除 3 时得到 715 827 882.33333 带有小数,此时 n % 3 != 0 直接脱离循环返回 false。

方法二:判断是否为最大 3 的幂的约数:时空复杂度:O(1)
class Solution {
    public boolean isPowerOfThree(int n) {
        return n > 0 && 1162261467 % n == 0;
    }
}

204、难度中等:

方法一:原创,耗时巨大:

质数定义:质数又称素数,是指在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。
在 10 以内:数字分为 2、3、5、7 这些质数和能被这些数整除的非质数
在 20 以内:数字分为 2、3、5、7、11、13、17、19 这些质数和能被这些数整除的非质数
也就是说,我们在遍历题给数 0 ~ n 时,每得到一个质数,就将其存在列表中,然后将下一个数与列表中的所有数进行取余,若等于 0 那就说明是非质数(因为下一个数的因数一定是从其前面的所有数中取出的)
由于 n 的数字不定,所以我将存储质数的列表选为了动态数组,以此产生的代码耗时远大于方法二的耗时,绝望.jpg

class Solution {

    ArrayList List = new ArrayList(); 

    public int countPrimes(int n) {
        if(n <= 2){
            return 0;
        }
        if(n == 3){
            return 1;
        }
        List.add(2);
        for(int i = 3; i < n; i++){
            if(isPrimes(i)){
                List.add(i); 
            }
        }
        return List.size();
    }

    public boolean isPrimes(int n){
        for(int i = 0;i < List.size(); i ++){
            if( n % (int)List.get(i) == 0){
                return false;
            }
        }
        return true;
    }
}
方法二:暴力

原理:考虑到如果 y 是 x 的因数,那么 x/y 也必然是 x 的因数,因此我们只要校验 y 或者 x/y 即可。而如果我们每次选择校验两者中的较小数,则不难发现较小数一定落在 [2, 根号x ] 的区间中,因此我们只需要枚举 [2, 根号x ] 中的所有数即可,这样单次检查的时间复杂度从 O(n) 降低至了 O(根号n)

class Solution {
    public int countPrimes(int n) {
        int ans = 0;
        for (int i = 2; i < n; ++i) {
            ans += isPrime(i) ? 1 : 0;
        }
        return ans;
    }

    public boolean isPrime(int x) {
        for (int i = 2; i * i <= x; ++i) {
            if (x % i == 0) {
                return false;
            }
        }
        return true;
    }
}

基于该方法的 ”只需要枚举 [2, 根号x ] 中的所有数“ 原理,再对方法一进行如下改进:此时方法一的耗时就短于方法二了

class Solution {

    public int countPrimes(int n) {
        if(n <= 2){
            return 0;
        }
        if(n == 3){
            return 1;
        }
        ArrayList List = new ArrayList();
        List.add(2);
        int count = 1;
        for(int i = 3; i < n; i++){
            if(isPrimes(i,List)){
                List.add(i);
            }
        }
        return List.size();
    }

    public boolean isPrimes(int n,ArrayList List){
        for(int i = 0;(int)List.get(i) * (int)List.get(i) <= n; i ++){
            if( n % (int)List.get(i) == 0){
                return false;
            }
        }
        return true;
    }
}
方法三:埃氏筛:

枚举没有考虑到数与数的关联性,因此难以再继续优化时间复杂度。接下来我们介绍一个常见的算法,该算法由希腊数学家厄拉多塞(EratosthenesEratosthenes)提出,称为厄拉多塞筛法,简称埃氏筛。
我们考虑这样一个事实:如果 xx 是质数,那么大于 x 的 x 的倍数 2x,3x,… 一定不是质数,因此我们可以从这里入手。
我们设 isPrime[i] 表示数 i 是不是质数,如果是质数则为 1,否则为 0。从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数(除了该质数本身),即 0,这样在运行结束的时候我们即能知道质数的个数。
这种方法的正确性是比较显然的:这种方法显然不会将质数标记成合数;另一方面,当从小到大遍历到数 x 时,倘若它是合数,则它一定是某个小于 x 的质数 y 的整数倍,故根据此方法的步骤,我们在遍历到 y 时,就一定会在此时将 x 标记为 isPrime[x]=0。因此,这种方法也不会将合数标记为质数。
当然这里还可以继续优化,对于一个质数 x,如果按上文说的我们从 2x 开始标记其实是冗余的,应该直接从 x⋅x 开始标记,因为 2x,3x… 这些数一定在 x 之前就被其他数的倍数标记过了,例如 2 的所有倍数,3 的所有倍数等。

class Solution {
    public int countPrimes(int n) {
        int[] isPrime = new int[n];
        // 初始化 isPrime 所有元素为 1,即全是质数
        Arrays.fill(isPrime, 1);
        int ans = 0;
        for (int i = 2; i < n; ++i) {
            // 从 i=2 开始遍历,因为 2 是质数,所以直接按 1 判断
            // 因为内部 if 判断了当前 i 倍数的合数,所以下一轮循环的 1 和 0 是准确的,可以直接按 1 判断
            if (isPrime[i] == 1) {
                ans += 1;
                // 若 i*i 小于 n,则可以选出一些还没被判定的合数:
                // 如 1*i、2*i 这类的在 i 是 1、2 时都已经被判定为合数了,所以直接从 i*i、(i+1)*i 开始判断
                if ((long) i * i < n) {
                    for (int j = i * i; j < n; j += i) {
                        isPrime[j] = 0;
                    }
                }
            }
        }
        return ans;
    }
}
方法四:线性筛:

此方法不属于面试范围范畴,详见:
作者:LeetCode-Solution
链接:https://leetcode-cn.com/problems/count-primes/solution/ji-shu-zhi-shu-by-leetcode-solution/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodeYello

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值