丑数
编写一个程序判断给定的数是否为丑数。
丑数就是只包含质因数 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]
任何一个丑数都可以表示为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
是丑数。 -
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;
}