剑指Offer_#49_丑数

剑指Offer_#49_丑数

Contents

题目

我们把只包含质因子 2、3 和 5 的数称作丑数(Ugly Number)。求按从小到大的顺序的第 n 个丑数。
示例:

输入: n = 10
输出: 12
解释: 1, 2, 3, 4, 5, 6, 8, 9, 10, 12 是前 10 个丑数。

说明:

  • 1是丑数。
  • n不超过1690。

思路分析

丑数的定义理解:

  • 注意题目中所说的质因子思考: 为什么8也是丑数?难道它不是可以分解为2*4吗?4难道不是一个非2,3,5的因子吗?原因: 4是8的因子,但是不是质因子,考虑的是质因子。
  • 这里有一个常识:任意合数(可以分解为除了1和自身的其他因子)都可以被分解为多个素数(质数)的积。

暴力循环

根据丑数的定义,我们可以判断任何一个数字是否是丑数。所以可以从1开始,判断每个正整数是否是丑数(不断取余,做除法),配合一个计数变量,就可以得到第n个丑数。
这种做法需要大量的取余和除法运算。

class Solution {
    public int nthUglyNumber(int n) {
        int uglyCount = 0;
        int num = 0;
        while(uglyCount < n){
            num++;
            if(isUgly(num)) uglyCount++;
        }
        return num;
    }

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

超时。但是这里的isUgly()方法值得借鉴,很明确的看出了如何判断丑数。

递推(通过已有的丑数找到新的丑数)

根据丑数的定义,每个丑数的只包含因子2,3,5,也就是说,丑数应该是另一个丑数乘以2、3、5的结果。通过这种方式,我们可以通过已有的丑数,找到新的丑数。
这种思路的关键在于如何保证得到的丑数序列是有序的?
假设我们已经得到了一个部分的丑数序列,为了获取下一个丑数(必须是刚好比当前丑数大的第一个丑数),有两个问题:

  • 找哪个数字来乘以2/3/5?
  • 乘2?还是乘3?还是乘5?

解决上述问题,采用三指针法
三个指针初始化指向第一个丑数1,然后分别计算三个指针指向的数字乘2/3/5的结果,将其中最小的结果加入丑数序列,后移对应的指针。

class Solution {
    public int nthUglyNumber(int n) {
        if(n == 0) return 0;
        //*2,*3,*5三个数组的指针
        int a = 0,b = 0,c = 0;
        int[] uglyNums = new int[n];
        uglyNums[0] = 1;
        for(int i = 1;i <= n - 1;i++){
            int tmp = Math.min(2*uglyNums[a],Math.min(3*uglyNums[b],5*uglyNums[c]));
            if(tmp == 2 * uglyNums[a]) a++;
            if(tmp == 3 * uglyNums[b]) b++;
            if(tmp == 5 * uglyNums[c]) c++;
            uglyNums[i] = tmp;
        }
        return uglyNums[n - 1];
    }
}

复杂度分析

时间复杂度O(n)
空间复杂度O(n)

思考:为什么不会漏掉丑数,或者出现重复的丑数?

一种粗暴的思路是直接对于当前已有的丑数数组里边的每个数字乘以2,3,5,然后去重,排序。
上述的三指针的思路看起来优雅很多,但是为什么不会漏掉,或者多计算出一些重复的丑数呢?

  1. 为什么不会漏掉?
    对于a,b,c三个指针,每次只取乘积结果最小的那个结果,然后把对应指针后移一位。那么不是最小的指针是不会动的,所以实际上粗暴解法里的每一个数字,三指针法里边都计算到了。所以不可能漏掉。
  2. 为什么每次得到的最小结果一定是比已有最大丑数更大的丑数?
    以下是运行过程中的一个状态,可以看到a最后指向5,上一次a'指向4,上次生成的最大丑数是末尾的8。
    由于数组是完全有序的,而且每个指针对应乘以的倍数是不变的,所以uglyNums[a]*2必然大于uglyNums[a']*2
    uglyNums[b]*3uglyNums[c]*5没有被选到,说明这两个也必然大于uglyNums[a']*2
1
2<--c(乘5的指针)
3<--b(乘3的指针)
4<--a'(乘2的指针)
5<--a(乘2的指针)
6
8
  1. 为什么不会出现重复?
    前面已经证明了,每一轮循环得到的丑数一定比已有最大丑数更大,这里是严格大于关系,不可能出现相等。
    那么出现重复只可能在一轮循环内部,也就是min函数比较的三者有两个是重复的,那么结果是这两个指针都会后移一次,所以结果当中不会生成2个重复的数字。
原文地址:https://www.cnblogs.com/Howfars/p/13335813.html