Ugly Number 问题及其扩展

780 阅读4分钟

首先,我们从最简单的开始:

Write a program to check whether a given number is an Ugly Number.

Ugly Numbers are positive numbers whose prime factors only include 2, 3, 5.

For example, 6, 8 are ugly while 14 is not ugly since it includes another prime factor 7.

Note that 1 is typically treated as an ugly number.

Ugly Number是指 一个正数的素因子只包含2,3,5. 比如 6和8就是Ugly Number,而14不是, 14 = 2 * 7 多了另外一个素因子7. 所以判断一个数字是不是Ugly Number就很简单了,就是这几个因子来回除。能整除就是Ugly Number,反之就不是。代码如下:

    public boolean isUgly(int num) {
            if(num <= 0) return false;
            if(num == 1) return true;

            while (num > 1) {
                if (num % 2 == 0) {
                    num = num / 2;
                } else if(num % 3 == 0) {
                    num = num / 3;
                } else if (num % 5 == 0) {
                    num = num / 5;
                } else
                    return false;
            }
            return true;
        }

那么继续考虑一个进阶问题:

Write a program to find the n-th Ugly Number.

Ugly Number are positive numbers whose prime factors only include 2,3, 5. For example, 1,2, 3, 4, 5, 6, 8, 9, 10, 12 is the sequence of the first 10 ugly numbers.

Note that 1 is typically treated as an ugly number.

我们升级一下来找一下第n个Ugly Number。对于任何一个Ugly Number k 那么 2 k, 3 k, 5 * k 都是Ugly Number。因此找第n个Ugly Number 就是,通过把一个Ugly Number跟素因子{2,3,5}乘积,不断的按照数字大小增加到列表后面,直到找到第n个Ugly Number为止。

    public int nthUglyNumber(int n) {
        if(n == 1) return 1;
        int[] uglyNumbers = new int[n];
        uglyNumbers[0] = 1;
        int idx2 = 0;
        int idx3 = 0;
        int idx5 = 0;
        int counter = 1;
        while(counter < n) {
            int min = Math.min(
                           Math.min(uglyNumbers[idx2]*2, uglyNumbers[idx3] * 3),
                        uglyNumbers[idx5] * 5);

            if(min == uglyNumbers[idx2] * 2) {
                idx2++;
            }
             if(min == uglyNumbers[idx3] * 3) {
                idx3++;
            }
            if(min == uglyNumbers[idx5] * 5) {
                idx5++;
            }
            uglyNumbers[counter] = min;
            counter++;
        }
        return uglyNumbers[n -1];
    }

把这个更进一步扩展的更通用一点:

Write a program to find the nth Super Ugly Number.

Super Ugly Numbers are positive numbers whose all prime factors are in the given prime list primes of size k. For example, [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] is the sequence of the first 12 Super Ugly Numbers given primes = [2, 7, 13, 19] of size 4.

Note:

(1) 1 is a super ugly number for any given primes.

(2) The given numbers in primes are in ascending order.

(3) 0 < k ≤ 100, 0< n ≤ 106, 0 < primes[i] < 1000.

原理同上不解释,直接上代码:

public int nthSuperUglyNumber(int n, int[] primes) {
        if( n == 1 ) return 1;

        ArrayList uglyNumbers = new ArrayList<>();
        uglyNumbers.add(1);
        int[] index = new int[primes.length];
        for(int i = 0; i < index.length; i++) {
            index[i] = 0;
        }
        int counter = 1;
        while(counter < n) {
            int min = Integer.MAX_VALUE;
            int minIndex = 0;
            for (int i = 0; i < primes.length; i++) {
                int currentVal = uglyNumbers.get(index[i]) * primes[i];
                if(currentVal < min) {
                    min = currentVal;
                    minIndex = i;
                }
            }
            index[minIndex]++;
           if (uglyNumbers.get(uglyNumbers.size() -1 ) != min) {
              //这个判断很重要,不然会有大量重复结果。
                uglyNumbers.add(min);
                counter++;
           }
        }
        return uglyNumbers.get(uglyNumbers.size() - 1);
    }

当然了,我这个代码写的比较low,有高手7行代码解决问题,膜拜一下:

    int nthSuperUglyNumber(int n, vector& primes) {
        vector index(primes.size(), 0), ugly(n, INT_MAX);
        ugly[0]=1;
        for(int i=1; i

Ugly Number 我们引申到质数相关的经典问题。

质数(Prime Number)又称素数,质素定义为大于1的自然数中,除了1和它本身以外再有其他因数的数。

质数问题有3个常见问题:

  1. 判断一个数是否是质数;
  2. 给一个自然数N,打印出小于N的所有质数;
  3. 给一个自然数N,打印出前N个质数;

后两个问题的答案以及效率跟第一个问题,即,你如何判断一个数是质数有关。

判断一个数N是否是质数最简单的办法是根据质数的定义,通过不断的除2 -- N-1之间的数字,看看是否能找到除1和自身之外的质因子。

进一步观察发现除了2之外质数一定是奇数,因为所有的偶数一定有质因子 2

再通过观察发现只要尝试3 -- N-1的开平方之间的数字就够了,因为因数都是成对出现的,比如:100 : 1 100 , 2 50, 4 25,5 20, 10 * 10 ,因此只要尝试到10就够了。

    bool isPrime(int n) {
        if(n < 2) return false;
        if(n == 2) return true;
        for(int i = 3; i*i <= n; i += 2)
             if(n%i == 0) return false;
        return true;
    }

最后,说一个求质数比较牛逼的方法,筛除法

这个方法是牛逼的数学家埃拉托斯特尼(Eratosthenes) 公元前276年--前194年提出的。

就是这个小老头


埃拉托斯特尼筛法.png

这个人有多牛逼呢,说个小成就吧,设计了经纬度,并且在2000多年前,用数学的方法测量出了地球直径。

说回 筛除法

因为2是质数,因此可以把2的倍数全部去掉;

接着往下数3是质数,把3的倍数全部去掉;

继续数最小的数是5,把5的倍数全部去掉,依此类推再把7的倍数全部去掉。这样不断的筛除,最后就把素数剩下了。

可能说的比较抽象,我们用动图来演示一下:


筛法演示图.gif

伪代码如下

    Input: an integer n > 1
    Let A be an array of Boolean values, indexed by integers 2 to n,
    initially all set to true.
        for i = 2, 3, 4, ..., not exceeding √n:
              if A[i] is true:
                for j = i2, i2+i, i2+2i, i2+3i, ..., not exceeding n:
                      A[j] := false
    Output: all i such that A[i] is true.

PS. Markdown 很好用