Java 算法-超级丑数(数学题)

  今天做了一道google的面试题,这道题是之前的丑数的变种,我们先来看看题意,然后在一步一步的理解最终的代码

题意

写一个程序来找第 n 个超级丑数。

超级丑数的定义是正整数并且所有的质数因子都在所给定的一个大小为 k 的质
数集合内。

比如给你 4 个质数的集合 [2, 7, 13, 19], 那么 [1, 2, 4, 7, 8, 13, 14, 16, 
19, 26, 28, 32] 是前 12 个超级丑数。

样例

给出 n = 6 和质数集合 [2, 7, 13, 19]。第 6 个超级丑数为 13,所以返回
 13 作为结果

注意事项

  1. 1 永远都是超级丑数不管给的质数集合是什么。
  2. 给你的质数集合已经按照升序排列。
  3. 0 < k ≤ 100, 0 < n ≤ 10^6, 0 < primes[i] < 1000

1.原来的丑数

  我们知道,原来的丑数的定义时:如果一个数所有质数因子都在{2,3,5}的范围里面,那么我们称这个数为丑数。

(1).判断一个数是否是丑数

  我们知道,如果一个数所有的质数因子都在{2,3,5}范围里面,那么意味着这个数肯定能被2,3,5或者是他们的倍数整除。那么我们就循环取余和取除,看看它最终是否为1,如果最终为1的话,表示这个数被2,3,4除尽了,如果不等于1的话,表示这个数还有其他的因子,因此不是丑数

public boolean isUgly(int num) {
        if(num == 0){
            return false;
        }
        while(num % 2== 0){
            num /=2;
        }
        while(num % 3 == 0){
            num /=3;
        }
        while(num % 5 == 0){
            num /= 5;
        }
        return num == 1;
    }

(2).求第n个丑数

  这个问题看上去是不是很简单,我们用一个死循环,判断从2 ~ 正无穷中所有的数是不是丑数,当计算到第n个时,就退出循环!但是你有没有发现,这样的话时间复杂度会非常的高。因此这个问题不能用常规的方法。
  我们这样来想,一个丑数,只能由{2,3,5}相乘得到。因此,我们可以来构造丑数:
  假设丑数数组nums[],其中nums[0] = 1。怎么构造之后的丑数,我们假设我们构造的丑数是排序的,那么我们知道,一个丑数肯定是另一个丑数乘以2、3或者5。我们假设nums数组里面已经有了一部分丑数(nums[0] = 1),那么之后的丑数肯定是前面的丑数乘以2、3或者5。怎么保证生成的丑数比当前的nums数组最后一个数字大,但是又是生成的三个丑数中的最小(分别乘以三个数字,所以生成了三个丑数)。我们有三个index来分别表示,当前乘以2、3或者5的位置,下一次还要乘以2、3或者5的话,就从num相应位置的丑数相乘就行。
  在每次取得最小的丑数,我们还要更新对应index,index需要前进一步:


Java 算法-超级丑数(数学题)_第1张图片

  因此,我们就可以用新的方法来写第n个丑数了:

private static int calulateUglyNumber(int n) {
        //记录丑数的数组
        int nums[] = new int[n];
        nums[0] = 1;
        // primes[0]表示2的位置
        // primes[1]表示3的位置
        // primes[2]表示5的位置
        int primes[] = new int[3];
        int count = 1;
        while (count < n) {
            int num = min(nums[primes[0]] * 2, nums[primes[1]] * 3, nums[primes[2]] * 5);
            
            //更新index
            if (num == nums[primes[0]] * 2) {
                primes[0]++;
            }
            if (num == nums[primes[1]] * 3) {
                primes[1]++;
            }
            if (num == nums[primes[2]] * 5) {
                primes[2]++;
            }
            nums[count++] = num;
        }
        Arrays.stream(nums).forEach((int a) -> System.out.print(a + " "));
        return 0;
    }
    //求三个数字的最小值
    private static int min(int a, int b, int c) {
        if (a > b) {
            a = b;
        }
        if (a > c) {
            a = c;
        }
        return a;
    }

2.超级丑数

  经过上面的方法解决第n个丑数的问题,那么我们来解决这个超级丑数的问题就非常的容易了。超级丑数无非就是把原来2,3,5变成了自定义的丑数因子。

public static int nthSuperUglyNumber(int n, int[] primes) {
        //记录丑数的数组
        int nums[] = new int[n];
        nums[0] = 1;
        //每个因子的位置
        int primesIndex[] = new int[primes.length];
        int count = 1;
        while (count < n) {
            int min = Integer.MAX_VALUE;
            for (int i = 0; i < primes.length; i++) {
                //求乘以每一个因子得到的最小丑数
                int temp = nums[primesIndex[i]] * primes[i];
                min = Math.min(temp, min);
            }
            for (int i = 0; i < primes.length; i++) {
                //更新index,可能更新多个index
                if (min == nums[primesIndex[i]] * primes[i]) {
                    primesIndex[i]++;
                }
            }
            nums[count++] = min;
        }
        return nums[count - 1];
    }

你可能感兴趣的:(Java 算法-超级丑数(数学题))