leetocode刷题-数论问题

49 阅读4分钟

跟孙哥学java

孙哥主页

数论是一个很重要的学科,覆盖领域极广,小到小学的智力问题,大到世界顶级科学家都一直在研究相 关问题,因此其难度跨度非常大。在程序设计里,也经常会出现数论的问题,但是,这些一般都是比较 基本的数论问题,例如素数问题、幂、对数、阶乘、幂运算、初等数论、几何问题、组合数学等等。这 些问题中,组合数学等适合在回溯里讲解。几何问题则过于繁琐,不利于做题。本部分,我们暂时只以 宿舍和合数的问题来讲解,后续找到合适的题目继续来补充。

辗转相除法

辗转相除法又叫做欧几里得算法,是公元前300年左右的希腊数学家欧几里得在他的著作《几何原本》提出 的。最大公约数(greatest common divisor,简写为gcd),是指几个数的共有的因数之中最大的一个, 例如8和12的最大公因数是4,记作gcd(8,12)=4。辗转相除法最重要的规则是,若r是a÷b的余数, 则gcd(a,b)=gcd(b,r)。例如计算gcd(546,429):

由于546=1(429)+117
429=3(117)+78
117=1(78)+39
78=2(39)
因此
gcd(546,429)
=gcd(429,117)
=gcd(117,78)
=gcd(78,39)
=39

参考www.zhihu.com/question/51…

public class Solution {
   public static void main(String[] args) {
      //a>b
      System.out.println(gcd(546,429));
   }

   private static int gcd(int a, int b) {
      int k=0;
      do{
         k=a%b;
         a=b;
         b=k;
      }while (k!=0);
      return a;
   }
}

素数与合数

我们看一下素数和合数的问题。素数又称为质数,素数首先要满足大于等于2,并且除了1和它本身之外, 不能被任何其他自然数整除。其他数都是合数。比较特殊的是1即非素数,也非合数。2是唯一的同时为偶 数和素数的数字。 有了定义,自然第一个问题就是如何判断一个正整数是否为素数。题目要求:给定一个正整数 n(n<10^9),判断它是否为素数。 基本的方式是从2开始依次与n取余测试,看看是否出现n%i==0的情况,如果出现了则说明当前的能被i 整除,所以就不是。理论上一直测试到-1,假如都不是,那就是素数了。 而事实上不需要测试这么多,只要从2开始遍历一直到n^(1/2)就可以,不用执行到-1。这个是有明确的 数学证明的,我们不再赘术,所以实现代码就是:

public class Solution {
   public static void main(String[] args) {
      System.out.println(isPrime(17));
   }

   private static boolean isPrime(int num) {
      int max= (int) Math.sqrt(num);
      for(int i=2;i<=max;i++){
         if(num%i==0){
            return false;
         }
      }
      return true;
   }
}

基于该基础,leetcode204 给定整数n,返回所有小于非负整数n的质数的数量 计数质数 给定整数 n ,返回 所有小于非负整数 n 的质数的数量image.png

class Solution {
    public int countPrimes(int n) {
        if(n<=2) return 0;
        int num=1;
        for(int i=3;i<n;i=i+2){
            if(isP(i)){
                num++;
            }
        }
        return num;

    }

    private boolean isP(int num) {
        int max= (int) Math.sqrt(num);
        for(int i=2;i<=max;i++){
            if(num%i==0){
                return false;
            }
        }
        return true;
    }
}

image.png 这个方法会超时,换下一种

埃氏筛

上面eetCode204的题找素数的方法虽然能解决问题,但是效率太低,能否有效率更高一些的方法呢? 解决这个题有一个有效的方法,叫做埃氏筛,后来又产生了线性筛,奇数筛等改进的方法。 基本思想是如果是质数,那么大于X的y的倍数2x.3x.一定不是质数,因此我们可以从这一点入手。 如下图所示 image.png

class Solution {
    
    public int countPrimes(int n) {
        boolean[] isPrim = new boolean[n];
        Arrays.fill(isPrim, true);
        // 从 2 开始枚举到 sqrt(n)。
        for (int i = 2; i * i < n; i++) {
            // 如果当前是素数
            if (isPrim[i]) {
                // 就把从 i*i 开始,i 的所有倍数都设置为 false。
                for (int j = i * i; j < n; j+=i) {
                    isPrim[j] = false;
                }
            }
        }

        // 计数
        int cnt = 0;
        for (int i = 2; i < n; i++) {
            if (isPrim[i]) {
                cnt++;
            }
        }
        return cnt;
    }
}

丑数问题

image.png image.png 根据丑数的定义,0和负整数一定不是丑数。 当n>0时,若n是丑数,则n可以写成n=a^xb^yc^z的形式,其中a,b,c都是非负整数。特别地,当 a,b,c都是000时,n=1。 为判断n是否满足上述形式,可以对n反复除以2,3,5,直到n不再包含质因数2,3,5。若刺下的数等于1,则说 明n不包含其他质因数,是丑数;否则,说明包含其他质因数,不是丑数。 因此可以得到如下解答方式:

class Solution {
    public static void main(String[] args) {
        for (int i = 0; i < 50; i++) {
            System.out.println("i:"+i+isUgly(i));
        }
    }
    public static boolean isUgly(int n) {
        if(n<=0) return false;
        while (n%2==0){
            n/=2;
        }
        while (n%3==0){
            n/=3;
        }
        while (n%5==0){
            n/=5;
        }
        return n==1;
    }
}