丑数

丑数

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

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

示例 1:

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

示例 2:

输入: 8
输出: true
解释: 8 = 2 × 2 × 2

示例 3:

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

说明:

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

任何一个丑数都可以表示为2i3j5k

迭代

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

递归

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

新代码:

public boolean isUgly(int n) {
    if (n <= 0) {
        return false;
    }
    int[] factors = {2, 3, 5};
    for (int factor : factors) {
        while (n % factor == 0) {
            n /= factor;
        }
    }
    return n == 1;
}

丑数 II

编写一个程序,找出第 n 个丑数。

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

示例:

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

说明:

  1. 1 是丑数。
  2. n 不超过1690。

方法一:暴力(超时)

public int nthUglyNumber(int n) {
    int i=0;
    int count=0;
    while (count

方法二:堆
预先计算 1690 个丑数

堆中包含一个数字开始:1,去计算下一个丑数

将 1 从堆中弹出然后将三个数字添加到堆中:1×2, 1 1×3,1×5

现在堆中最小的数字是 2,为了计算下一个丑数,要将 2 从堆中弹出然后添加三个数字:2×2, 2×3,2×5

重复该步骤计算所有丑数。在每个步骤中,弹出堆中最小的丑数 k,并在堆中添加三个丑数:k×2, k×3,k×5

int[] nums = new int[1690];

Solution() {
    PriorityQueue queue = new PriorityQueue<>();//要用long,因为队列中的元素比数组中要多,*2、*3可能会越界
    queue.offer(1L);
    Set set = new HashSet<>();//用于去重 如6可能由弹出的2*3组成,也可能由弹出的3*2组成
    set.add(1L);
    int[] primes = {2, 3, 5};
    int i = 0;
    while (i < 1690) {
        long num = queue.poll();
        for (int prime : primes) {
            if (!set.contains(num * prime)) {
                queue.offer(num * prime);
                set.add(num * prime);
            }
        }
        nums[i++] = (int) num;
    }
}

public int nthUglyNumber(int n) {
    return nums[n - 1];
}

方法三:动态规划
从数组中只包含一个丑数数字 1 开始,使用三个指针p2,p3,p5,标记所指向丑数要乘以的因子

在2×nums[p2],3×nums[p3],5×nums[p5]中选取最小的添加到数组中,并移动相应位置的指针

重复该步骤直到计算完 1690 个丑数

int[] nums = new int[1690];

Solution() {
    int p2 = 0, p3 = 0, p5 = 0;
    nums[0] = 1;
    int i = 1;
    while (i < 1690) {
        int num = Math.min(2 * nums[p2], Math.min(3 * nums[p3], 5 * nums[p5]));
        nums[i++] = num;
        if (num == nums[p2] * 2) {
            p2++;
        }
        if (num == nums[p3] * 3) {
            p3++;
        }
        if (num == nums[p5] * 5) {
            p5++;
        }
    }
}

public int nthUglyNumber(int n) {
    return nums[n - 1];
}

不用预先计算

public int nthUglyNumber(int n) {
    int[] dp = new int[n];//表示第i+1个丑数
    int p2 = 0, p3 = 0, p5 = 0;
    dp[0] = 1;
    for (int i=1;i 

丑数 III

请你帮忙设计一个程序,用来找出第 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, 12... 其中第 4 个是 6

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

示例 4:

输入:n = 1000000000, a = 2, b = 217983653, c = 336916467
输出:1999999984

提示:

  • 1 <= n, a, b, c <= 10^9
  • 1 <= a * b * c <= 10^18
  • 本题结果在 [1, 2 * 10^9] 的范围内

思考:对于一个丑数x,如何确定它是第几个丑数——只需要计算X中包含了多少个丑数因子

即只需要知道在[0,X]范围内,还有多少个丑数,而这些丑数,无非就是一些能被a或者b或者c所整除的数

那么用X/a、X/b、X/c就能计算出[0,X]范围内有多少数能被a或者b或者c整除,然后把它们加起来就是答案

但是可能存在重复计算:如果一个数既能被a整除,又能被b整除,那么实际上该数在先前的计算中就被重复计算了一次(分别是在计算X/a和X/b时)。

分析情况:

  • 该数只能被a整除 (该数一定是a 的整数倍)
  • 该数只能被b整除 (该数一定是b 的整数倍)
  • 该数只能被c整除 (该数一定是c 的整数倍)
  • 该数只能被a和b同时整除 (该数一定是a、b最小公倍数的整数倍)
  • 该数只能被a和c同时整除 (该数一定是a、c最小公倍数的整数倍)
  • 该数只能被b和c同时整除 (该数一定是b、c最小公倍数的整数倍)
  • 该数只能被a和b和c同时整除(该数一定是a、b、c的最小公倍数的整数倍)

所以,我们只需要分别计算以上七项就能得到结果了!让我们分别来看(用MCM+下标表示最小公倍数):
情况1 = X/a - 情况4 - 情况5 - 情况7
情况2 = X/b - 情况4 - 情况6 - 情况7
情况3 = X/c - 情况5 - 情况6 - 情况7
情况4 = X/MCM_a_b - 情况7
情况5 = X/MCM_a_c - 情况7
情况6 = X/MCM_b_c - 情况7
情况7 = X/MCM_a_b_c

让我们整理上述方程后也就得到:
sum(情况) =X/a + X/b + X/c - X/MCM_a_b - X/MCM_a_c - X/MCM_b_c + X/MCM_a_b_c

最小公倍数 = a*b/(a和b的最大公约数),最大公约数可以通过辗转相除法得到

二分搜索

在得到了计算任意数中包含了多少个丑数因子的方法后,我们实际上只需要通过二分法,不断缩小边界范围,直到某个位置所对应的数恰好包含了n个丑数因子为止。

  • 先找到a,b,c里最小的那个数,比如是a,那么第n个丑数肯定是<= n * a
  • 然后就开始二分法的做法了,将n*a置为上限high,a置为下限low
  • 求解mid = (low+high)/2这个数里包含了多少丑数(并且mid也为丑数):
    • 如果上一步的数字等于n,那最好啦,判断当前的mid是否是丑数,如果是,直接返回mid,如果不是,将high=mid - 1
    • 如果上一步的数字大于n,将high=mid - 1
    • 如果上一步的数字小于n,将low=mid + 1
public int nthUglyNumber(int n, int a, int b, int c) {
    long low = Math.min(a, Math.min(b, c));//用long防止乘法 low+high溢出
    long high = n * low;
    return binarySearch(low, high, n, a, b, c);
}

public int binarySearch(long low, long high, long n, int a, int b, int c) {
    while (low < high) {
        long mid = (low + high) / 2;
        int num = (int) (mid / a + mid / b + mid / c - mid / mcm(a, b) - mid / mcm(a, c) - mid / mcm(b, c) + mid / mcm(a, mcm(b, c)));
        if (num == n && (mid % a == 0 || mid % b == 0 || mid % c == 0)) {
            return (int) mid;
        } else if (num < n) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return (int) low;
}

public long mcm(long a, long b) {
    long s = a * b;
    while (b > 0) {
        long tmp = a % b;
        a = b;
        b = tmp;
    }
    return s / a;
}

你可能感兴趣的:(丑数)