丑数

118 阅读4分钟

---

主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow, v-green, vue-pro, healer-readable, mk-cute, jzman, geek-black

贡献主题:github.com/xitu/juejin…

theme: juejin highlight:

LC 263 丑数

编写一个程序判断给定的数是否为丑数。

丑数就是只包含质因数 2, 3, 5 的正整数。

示例 1:

输入: 6 输出: true 解释: 6 = 2 × 3 示例 2:

输入: 8 输出: true 解释: 8 = 2 × 2 × 2 示例 3:

输入: 14 输出: false 解释: 14 不是丑数,因为它包含了另外一个质因数 7。 说明:

1 是丑数。 输入不会超过 32 位有符号整数的范围: [−231, 231 − 1]

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ug… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

public boolean isUgly(int num) {
    		// 丑数是只包含质因数2,3,5的正整数,非正整数返回false
        if (num < 1) return false;
        while (num % 5 == 0) num /= 5;
        while (num % 3 == 0) num /= 3;
        while ((num & 1) == 0) num >>>= 1;
    		// 能被2,3,5整除就不断相除,最后余数是1则是丑数,否则不是。
        return num == 1;
    }

丑数二

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

示例:

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

1 是丑数。 n 不超过1690。

暴力求解(超时)

class Solution {
    public int nthUglyNumber(int n) {
        for (int i = 1; ;i++) {
            if (isUgly(i) && (--n == 0)) return i;
        }
    }
    public boolean isUgly(int num) {
        if (num < 1) return false;
        while (num % 5 == 0) num /= 5;
        while (num % 3 == 0) num /= 3;
        while ((num & 1) == 0) num >>>= 1;
        return num == 1;
    }
}

三指针

由丑数的定义:一个只有质因数2,3,5的正整数。可以推出一个丑数乘以2、3 或 5 必然也是一个丑数。所有丑数(a > 1)可以分为ABC三个数组。

A: {1*2, 2*2, 3*2, 4*2, ...... ,(n-1)*2, n*2}

B: {1*3, 2*3, 3*3, 4*3, ...... ,(n-1)*3, n*3}

C:{1*5, 2*5, 3*5, 4*5, ...... ,(n-1)*5, n*5}

那么所有丑数就是以上三个数组去重合并后的结果。至此问题转换成了如何对三个有序数组去重合并元素?

合并有序数组一个比较好的办法是,声明 i, j, k 三个指针分别指向三个数组头地址,然和比较三个指针指向元素的大小关系,取最小值。计入丑数数组,相应的指针向前移动一位。考虑到出现多个相同最小元素的情况,如 A[3*2] == B[2*3] 多个相同最小值的指针都前进一位。因此代码实现中使用的是多个 if 而不是 if else。

这里的 A B C 数组实际就是已保存丑数数组的 *2 *3 *5。注意边界测试用例 n=1 也是丑数。

代码如下:

public int nthUglyNumber(int n) {
        if (n < 1) return 0;
        int[] ugly = new int[n]; ugly[0] = 1;
        int i=0, j=0, k=0;
        for (int idx=1; idx < n; idx++) {
            int tmp = Math.min(ugly[i]*2, Math.min(ugly[j]*3, ugly[k]*5));
            if (tmp == ugly[i]*2) i++;
            if (tmp == ugly[j]*3) j++;
            if (tmp == ugly[k]*5) k++;
            ugly[idx] = tmp;
        }
        return ugly[n-1];
    }

丑数三

请你帮忙设计一个程序,用来找出第 n 个丑数。

丑数是可以被 a 或 b 或 c 整除的 正整数。

示例 1:

输入:n = 3, a = 2, b = 3, c = 5 输出:4 解释:丑数序列为 2, 3, 4, 5, 6, 8, 9, 10... 其中第 3 个是 4。 示例 2:

输入:n = 4, a = 2, b = 3, c = 4 输出:6 解释:丑数序列为 2, 3, 4, 6, 8, 9, 10, 12... 其中第 4 个是 6。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ug… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

贴上大佬的题解 二分法思路剖析

class Solution {
    public int nthUglyNumber(int n, int a, int b, int c) {
        if (a == 1 || b == 1 || c == 1) return n;

        // 两两组合的最小公倍数
        long lcmAB = lcm(a, b);
        long lcmAC = lcm(a, c);
        long lcmBC = lcm(b, c);
        // 三个的最小公倍数
        long lcm = lcm(lcmAB, c);

        int min = Math.min(c, Math.min(a, b));
        long left = min, right = (long) Math.pow(min, n);
        while (left <= right) {
            long mid = left + (right - left) / 2;
            long count = 
            (mid/a) + (mid/b) + (mid/c) - (mid/lcmAB) - (mid/lcmAC) - (mid/lcmBC) + (mid/lcm);
            if (count == n) {
                left = mid;
                break;
            } else if (count > n) {
                right = mid - 1;
            } else {
                left = mid + 1;
            }
        }
        return (int) (left - Math.min(Math.min(left % a, left % b), left % c));
    }

    // 求最小公倍数
    private long lcm(long a, long b) {
        return a * b / gcd(a, b);
    }

    // 辗转相除法求最大公约数
    private long gcd(long a, long b) {
        // b 是余数,a 是除数
        if (a == 0) return b;
        return gcd(b % a, a);
    }
}

超级丑数

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

超级丑数是指其所有质因数都是长度为 k 的质数列表 primes 中的正整数。

示例:

输入: n = 12, primes = [2,7,13,19] 输出: 32 解释: 给定长度为 4 的质数列表 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。 说明:

1 是任何给定 primes 的超级丑数。 给定 primes 中的数字以升序排列。 0 < k ≤ 100, 0 < n ≤ 106, 0 < primes[i] < 1000 。 第 n 个超级丑数确保在 32 位有符整数范围内。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/su…

public int nthSuperUglyNumber(int n, int[] primes) {
        // dp[i]代表:第i个丑数
        int[] dp = new int[n]; dp[0] = 1;
        // index[i]代表:primes[i]要和dp数组相乘的dp数组下标
        int[] index = new int[primes.length];
        for (int i = 1; i < n; i++) {
            int min = Integer.MAX_VALUE;
            for (int j = 0; j < index.length; j++) {
                min = Math.min(min, (primes[j] * dp[index[j]]));
            }
            for (int j = 0; j < index.length; j++) {
                // primes[j]*index[j]计算结果=本次循环最小值的index[j]++
                if (min == (primes[j] * dp[index[j]])) {
                    index[j]++;
                }
            }
            dp[i] = min;
        }
        return dp[n-1];
    }