丑数就是只包含质因数 2,3,5的正整数。
class Solution {
public boolean isUgly(int num) {
int temp;
if(num < 0)
return false;
while(num != 1){
temp = num;
num = num%2 == 0 ? num/2 : num;
num = num%3 == 0 ? num/3 : num;
num = num%5 == 0 ? num/5 : num;
if(temp == num)
return false;
}
return true;
}
}
方法一:
暴力超时,毕竟丑数还是少数,自己测了下,第1362个丑数就是424673280了。
class Solution {
public int nthUglyNumber(int n) {
int count = 0;
int i = 0;
for (; i < Integer.MAX_VALUE; i++) {
if(isUgly(i)) {
count++;
if(count == n) {
break;
}
}
}
return i;
}
public boolean isUgly(int num) {
int temp;
while(num != 1){
temp = num;
num = num%2 == 0 ? num/2 : num;
num = num%3 == 0 ? num/3 : num;
num = num%5 == 0 ? num/5 : num;
if(temp == num)
return false;
}
return true;
}
}
方法二:
丑数既然是包含质因数 2,3,5的正整数,那么一个丑数乘以2,3,5应该还是丑数,我们从1开始构造丑数,往下找就行了,关键是如何构造的时候不重复。
可以使用PriorityQueue构造最小堆
class Solution {
public int nthUglyNumber(int n) {
//优先队列默认排序规则是从小到大排序
//必须用Long,测过用Integer会溢出
PriorityQueue<Long> pq = new PriorityQueue<Long>();
int[] divisor = new int[] {2, 3, 5};
long[] res = new long[n]; //存丑数
res[0] = (long) 1;
for (int i = 0; i < n; i++) {
for (int j = 0; j < divisor.length; j++) {
Long temp = (Long) (res[i]*divisor[j]);
if(!pq.contains(temp)) {
pq.add(temp);
}
}
//从队头取一个最小的丑数,并把它从队列删除
if (i+1<res.length){
res[i+1] = pq.poll();
}
}
return (int) res[n-1];
}
}
实际时间复杂度为O(n^2*divisior.length),优先队列的方法contains时间复杂度为O(n),但是由于n的个数较小,还是可以通过的。
ps:
//再瞅一眼contains的源码
//优先队列继承的是object对象的方法,用的是顺序查找,优先队列排序的准则是不一定的,jdk没法用折半查找
private int indexOf(Object o) {
if (o != null) {
for (int i = 0; i < size; i++)
if (o.equals(queue[i]))
return i;
}
return -1;
}
方法三:
动态规划+三指针:
没有想到,翻别人题解,学到了学到了orz
public int nthUglyNumber(int n) {
int[] dp = new int[n]; //dp保存按序排列的丑数
dp[0] = 1;
int i2 = 0, i3 = 0, i5 = 0;
for (int i = 1; i < n; i++) {
int min = Math.min(dp[i2] * 2, Math.min(dp[i3] * 3, dp[i5] * 5));
if (min == dp[i2] * 2) i2++;
if (min == dp[i3] * 3) i3++;
if (min == dp[i5] * 5) i5++;
dp[i] = min;
}
return dp[n - 1];
}
这个思路跟之前的不同在于,之前是将最小的数分别乘以2,3,5都放进队列中判断,会有很大可能的重复,从而浪费时间,而DP的神仙之处在于可以同时选取多个数进行比较,并且乘因子的时候2,3,5是异步的,选取最小的,也不需要重新排序了。
时间复杂度为O(n)
三指针变成了primes.length个指针。
class Solution {
public int nthSuperUglyNumber(int n, int[] primes) {
int[] index = new int[primes.length];
int[] dp = new int[n];
dp[0] = 1;
for (int i = 1; i < n; i++) {
dp[i] = Integer.MAX_VALUE;
for (int j = 0; j < primes.length; j++) {
while(dp[index[j]]*primes[j] <= dp[i-1]) index[j]++;
dp[i] = Math.min(dp[i], dp[index[j]]*primes[j]);
}
}
return dp[n-1];
}
}