剑指Offer-35-丑数

题目

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

解析

思路一

暴力解法,就是从1开始遍历,若当前数是丑数,则计数器加1,否则继续前进,直到计数器等于给定的索引,返回当前丑数。
这个做法的缺点就是复杂度高,对于每一个数都要进行丑数判断操作。

    public static boolean isUglyNumber(int number) {
        while(number % 2 == 0) {
            number /= 2;
        }
        while(number % 3 == 0) {
            number /= 3;
        }
        while(number % 5 == 0) {
            number /= 5;
        }
        return number == 1;
    }

    public static int GetUglyNumber_Solution(int index) {
        if(index < 7) {
            return index;
        }
        int count = 0;
        for(int i = 7; ; i++) {
            if(isUglyNumber(i)) {
                count++;
                if(count == index) {
                    result[index] = i;
                    return i;
                }
            }
        }
    }

思路二

我们只需找出如何产生丑数即可,这样就可以依次产生一个丑数,直到个数等于给定索引即可。
根据题意可以,一个数只有质因子2,3,5的是丑数,那么如果一个数是丑数,它乘以2,3,5的结果也必为丑数啊。所以问题转化为如何根据现有的丑数集合,产生一个新的丑数且刚刚大于原丑数集合的最大值,从而把新的丑数添加到现有的丑数集合,形成新的丑数集合。
如果没有什么思路,可以举例子啊:
1. 刚开始丑数集合只有1
2. 1乘以2,3,5得到2,3,5(3个新的丑数),取最小的丑数添加到现有的集合,所以现在的丑数为1,2
3. 为了产生新的丑数,我们又要比较1乘以3,5,与2乘以2,3,5的结果中寻找新的最小丑数。显然新的丑数为3。

通过第3步我们发现,对于2来说,我们只需计算2乘2的即可,不用计算2乘3,5的结果,因为它一定比1乘3,5的结果大。我们的1的3,5还没有考察完呢。
所以我们的思路就是用3个指针分别指向待乘2,3,5的最小值,只有其中某一指针当乘以对应待乘项的产生的丑数添加到新的丑数集合后,才将该指针先后移一位,表示这个数乘以对应的乘项已考察,移动该指针到下一个丑数上。
我们这样始终都可以使每一个丑数都被这3个指针考察。3个指针分别指向在各自乘项上形成最小丑数的位置,这样我们只需对3个指针再取最小值,即可找到下一个丑数。然后更新上一步取最小值的指针,使其后移。

    public static int GetUglyNumber_Solution2(int index) {
        if(index < 7) {
            return 7;
        }
        int[] result = new int[index];
        result[0] = 1;
        int factor2 = 0;
        int factor3 = 0;
        int factor5 = 0;
        for(int i = 1; i < index; i++){
            int nextUglyNumber = Math.min(Math.min(result[factor2] * 2, result[factor3] * 3),
                    result[factor5] * 5);
            result[i] = nextUglyNumber;
            if (result[factor2] * 2 == nextUglyNumber) {
                factor2++;
            }
            if (result[factor3] * 3 == nextUglyNumber) {
                factor3++;
            }
            if (result[factor5] * 5 == nextUglyNumber) {
                factor5++;
            }
        }
        return result[index - 1];
    }

思路三

申请3个队列,用于分别存放乘以2,3,5得到的丑数队列。
1. 初始3个队列分别为2,3,5
2. 从3个队列中选出最小值作为新的丑数
3. 对于新的丑数:
1. 若是从2的队列出取出,则乘以2添加到2队列中,乘3添加3队列中,乘以5添加到5队列中,同时从队列头部剔除该丑数;
2. 若是从3的队列中取出,则乘以3添加到3队列汇中,乘以5添加到5队列中,同时从队列头部剔除该丑数;
3. 若是从5的队列中取出,乘以5添加到5队列中,同时从队列头部剔除该丑数;

步骤3是确保3个队列中不会出现重复元素。比如若是从3的队列中取出,不往2队列添加该数乘以2的原因是:乘以2的结果的丑数已经早早添加到了3队列中,不需要添加2队列中了。因为比如3乘2是6,而6早在2乘3已被添加到3队列中了。6除以3的结果必然比3小,所以出现的早。

    public static int GetUglyNumber_Solution3(int index) {
        if(index < 7) {
            return index;
        }
        Queue<Integer> factor2 = new LinkedList<>();
        Queue<Integer> factor3 = new LinkedList<>();
        Queue<Integer> factor5 = new LinkedList<>();
        factor2.add(2);
        factor3.add(3);
        factor5.add(5);
        int result = 0;
        for(int i = 2; i <= index; i++){
            result = Math.min(Math.min(factor2.peek(), factor3.peek()), factor5.peek());
            if(result == factor2.peek()) {
                factor2.add(result * 2);
                factor3.add(result * 3);
                factor2.poll();
            }else if(result == factor3.peek()) {
                factor3.add(result * 3);
                factor3.poll();
            }else {
                factor5.poll();
            }
            factor5.add(result * 5);
        }
        return result;
    }

总结

多往如何直接计算丑数的方向上想,思路二类似动态规划,即新的丑数必有之前的丑数乘以2,3,5得到。

猜你喜欢

转载自blog.csdn.net/dawn_after_dark/article/details/81490840