[LeetCode]-数学-1

前言

记录 LeetCode 刷题中遇到的数学相关题目,第一篇

204. 计数质数

质数是数论中很重要的一部分,应该也是大多数人刚接触数论时接触到第一个知识点。下面根据官方题解总结一下几种判断质数或者筛质数的方法:

  1. 最原始的做法。质数的 定义 就是,在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。所以对于每个数 x,我们可以从小到大枚举 [2,x−1] 中的每个数 y,判断 y 是否为 x 的因数,只要枚举到有一个是,那就说明 x 不是质数。这种方法,对于判断 n 个数有多少质数的情况,时间复杂度为 O(n²)。这种方法有一个优化的地方在于,不需要判断 [2,x−1],判断 [ 2 , x ] [2,\sqrt{x}] [2,x ] 即可
  2. 埃氏筛:如果 x 是质数,那么大于 x 的 x 的倍数 2x,3x,… 一定不是质数,所以可以从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数 (除了该质数本身)。这个方法也可以优化:如果 x 是质数,只需从 x * x 开始标记为合数即可,因为从 2x 开始到 x * x 之间的合数一定在枚举到比 x 小的质数时就已经筛过了
  3. 线性筛:相较于 埃氏筛,在枚举时多维护一个当前得到的质数集合。与 埃氏筛 不同的是,合数标记过程 不再仅当 x 为质数时才进行,而是对每个整数 x 都进行。对于整数 x,我们不再标记其所有的倍数,而是只 标记质数集合中的数与 x 相乘得到的数为合数,也就是对于每一个 x,枚举质数集合,将每个质数与 x 的积标为合数,且在发现 x 能整除某个质数的时候结束本次标记过程

下面是线性筛的做法:

public int countPrimes(int n) {
    
    
    List<Integer> primes = new ArrayList<Integer>(); //质数集合
    int[] isPrime = new int[n];                      //标记质数
    Arrays.fill(isPrime, 1);
    for (int i = 2; i < n; ++i) {
    
    
        if (isPrime[i] == 1) {
    
    
            primes.add(i);
        }
        //注意如果 i * primes.get(j) >= n 直接跳出循环
        for (int j = 0; j < primes.size() && i * primes.get(j) < n; ++j) {
    
    
            isPrime[i * primes.get(j)] = 0;
            //遇到能整除的质数结束标记过程
            if (i % primes.get(j) == 0) {
    
    
                break;
            }
        }
    }
    return primes.size();
}

326. 3 的幂

根据数据范围,n 不大于 231 - 1 的最大取值为 319 = 1162261467,那么如果 n 是 3 的幂,即 n = 3x,那么一定满足 n * 3k = 319,其中 0 <= k <= 19,亦即 319 % n = 0,所以只需判断这个等式是否成立即可。还要判断 n 是否大于 0:

public boolean isPowerOfThree(int n) {
    
    
    return n > 0 && 1162261467 % n == 0;
}

1262. 可被三整除的最大和

remain 数组中,remain[0] 记录被遍历过的元素中,被三除后余 0 的元素最大和;remain[[1] 记录被三除后余 1 的元素最大和;remain[2] 记录被三除后余 2 的元素最大和。当把所有元素遍历完后,remain[0] 就是本题答案

public int maxSumDivThree(int[] nums) {
    
    
    int len = nums.length;
    int[] remain = new int[3];
    int a,b,c;
    for(int i = 0; i < len; i++){
    
    
        a = remain[0] + nums[i];
        b = remain[1] + nums[i];
        c = remain[2] + nums[i];
        remain[a % 3] = Math.max(remain[a % 3], a);
        remain[b % 3] = Math.max(remain[b % 3], b);
        remain[c % 3] = Math.max(remain[c % 3], c);
    }
    return remain[0];
}

171. Excel表列序号

字符串转化为数字,相当于 26 进制转化为十进制。
但十进制中没有 0,‘A’ 对应的是 1 而不是 0,所以将字母转化为数字时还需要加一,即 字母 - ‘A’ + 1 而不是 字母 - ‘A’

public int titleToNumber(String columnTitle) {
    
    
    int len = columnTitle.length();
    int ans = 0;
    for(int i = 0; i < len; i++){
    
    
        ans = ans * 26 + (columnTitle.charAt(i) - 'A' + 1);
    }
    return ans;
}

168. Excel表列名称

171 题的思路逆过来即可。一开始是没做 171 题先做了 168 题,对这个 “cn–” 语句不能理解。然后就先做了 171 题,171 中的 “columnTitle.charAt(i) - ‘A’ + 1” 还是挺好理解的

cn 每次减一后对 26 取余得到的就是 171 中 " columnTitle.charAt(i) - ‘A’ ",所以再加 ‘A’ 得到的就是对应位上的字母。以此类推,将所有字母使用 StringBuilder 连接起来。连接的顺序是从低位到高位, reverse 成高位到低位即可

public String convertToTitle(int cn) {
    
    
    StringBuilder sb = new StringBuilder();
    while (cn > 0) {
    
    
        cn--;
        sb.append((char)(cn % 26 + 'A'));
        cn /= 26;
    }
    return sb.reverse().toString();
}

292. Nim 游戏

如果轮到我的时候剩下 0 个石头,那我必输;如果剩下 1 个,那我可以拿 1 个,轮到对面没有石头拿,对面必输;如果剩下 2 个,那我可以拿 2 个,轮到对面没有石头拿,对面必输;如果剩下 3 个,那我可以拿 3 个,轮到对面没有石头拿,对面必输;如果剩下 4 个,那我不管拿 1 个还是 2 个还是 3 个,剩下的石头对面都可以一次性拿完,再次轮到我没有石头拿,我必输;如果剩下 5 个,我可以只拿 1 个,剩下 4 个给对面,跟剩下 4 个给我一样,对面必输…可以发现规律,如果 n 取余 4 为 0,那么我必输

public boolean canWinNim(int n) {
    
    
    return n % 4 != 0;
}

470. 用 Rand7() 实现 Rand10()

randX() 可以得到 [1,X],那么 randX() - 1 为 [0,X - 1],(randX() - 1) * Y 为 [0,(X - 1)Y],那么 (randX() - 1) * Y + randY() 即为 [1,X*Y]

故已有 randX(),randY(),可以通过 (randX() - 1) * Y + randY() 得到 randX*Y

所以对于这道题,一开始已有 rand7,可以得到 rand49,如果结果在 [1,40] 中,就可以取余 10 然后加 1 得到 rand10。如果结果在 [41,49],就可以减去 40,得到 [1,9],即 rand9,可以跟 rand7 得到 rand63。同理如果结果在 [1,60] 可以得到 rand10,否则就是得到 rand3,可以跟 rand7 得到 rand21。此时如果结果在 [1,20] 可以得到 rand10,否则只剩下 1 这一个结果,无法继续得到新的 rand,只能从头开始循环

public int rand10() {
    
    
    while(true) {
    
    
        int rand7_1 = rand7();
        int rand7_2 = rand7();
        int r1 = (rand7_1 - 1) * 7 + rand7_2; //rand 49
        if(r1 <= 40) return r1 % 10 + 1; //rand 10

        r1 -= 40; //rand 9
        int rand7_3 = rand7();
        int r2 = (r1 - 1) * 7 + rand7_3; //rand 63
        if(r2 <= 60) return r2 % 10 + 1; //rand 10

        r2 -= 10; //rand 3
        int rand7_4 = rand7();
        int r3 = (r2 - 1) * 7 + rand7_4; //rand 21
        if(r3 <= 20) return r3 % 10 + 1; 
        
        //此时r3 - 20是1,说明后续无法继续去找rand X*Y了,只能重新开始循环
    }
}

50. Pow(x, n)

计算 xn,如果是按照 n 个 x 相乘的思路,复杂度为 O(n);而快速幂基于分治的思想,xn 会等于 xn/2 的平方,因此可以先计算出 xn/2 然后进行一次平方运算得到 xn,同理,xn/2 又等于 xn/4,因此可以先计算出 xn/4 然后进行一次平方运算得到 xn/2,以此类推,因此总的复杂度为 O(logn),从而减少运算时间

要注意的是,如果 n 为奇数,那么 xn 应该等于 xn/2 * xn/2 * x,因此需要判断进行平方根的平方运算时需不需要多乘一个 x

class Solution {
    
    
    public double myPow(double x, int n) {
    
    
        return n >= 0 ? quickPow(x, n) : 1.0 / quickPow(x, -n);
    }
    public double quickPow(double x, long n) {
    
    
        if (n == 0) {
    
    
            return 1.0;
        }
        //计算平方根
        double tmp = quickPow(x,n / 2);
        return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;
    }
}

猜你喜欢

转载自blog.csdn.net/Pacifica_/article/details/125566556
今日推荐