法一(动态规划 + 三指针)
/**
* 法一(动态规划 + 三指针)
* 1. 思路
* (1)因为丑数只包含质因数2、3、5,所以对于下个丑数来说,一定是前面某个丑数乘3、乘3或者乘5所得
* (2)定义三个指针p2、p3、p5,它们指向的数只能乘 2、3、5
* (3)在循环过程中,每次选取2*dp[p2]、3*dp[p3]、5*dp[p5]这三个数中结果最小的数,并且将对应的指针向前移动
* (4)有效循环是n次,当循环结束后,dp数组中就按从小到大的顺序保存了丑数
* 2. 动态规划
* (1)dp[i]代表第i-1个丑数
* (2)最小的丑数是1,因此dp[0]=1;
* 3. 复杂度
* (1)时间复杂度 O(n)
* (2)空间复杂度 0(n)
*
* @param n
* @return
*/
public int nthUglyNumber(int n) {
int[] dp = new int[n];
dp[0] = 1; // 初始化dp数组
int p2 = 0, p3 = 0, p5 = 0; // 初始化指针p2、p3、p5为0
for (int i = 1; i < n; i++) {
dp[i] = Math.min(2 * dp[p2], Math.min(3 * dp[p3], 5 * dp[p5]));
if (dp[i] == 2 * dp[p2]) { // 说明前p2个丑数*2也不可能产生比i更大的丑数了,所以移动p2
p2++;
}
if (dp[i] == 3 * dp[p3]) { // 说明前p3个丑数*3也不可能产生比i更大的丑数了,所以移动p3
p3++;
}
if (dp[i] == 5 * dp[p5]) { // 说明前p5个丑数*5也不可能产生比i更大的丑数了,所以移动p5
p5++;
}
}
return dp[n - 1];
}
法二(优先队列)
/**
* 法二(优先队列)
* 1. 思路
* (1)先创建一个优先队列,因为第一个丑数是1,所以把1加入优先队列
* (2)接下来不断地取出优先队列的队头元素,再分别乘以2/3/5后依次入队,最终第n个取出的数即为答案
* 2. 复杂度
* (1)时间复杂度 O(nlogn)
* (2)空间复杂度 0(n)
*
* @param n
* @return
*/
public int nthUglyNumber_2(int n) {
PriorityQueue<Long> heap = new PriorityQueue<>();
heap.add(1L);
long front = 1L;
while (n-- > 1) {
heap.add(heap.peek() * 2);
heap.add(heap.peek() * 3);
heap.add(heap.peek() * 5);
heap.poll();
while (heap.peek() == front) { // 若本次取出的和上次取出的一样,则不计数
heap.poll(); // 删除重复的
}
front = heap.peek();
}
return heap.peek().intValue();
}
本地测试
/**
* 264. 丑数 II
*/
lay.showTitle(264);
Solution264 sol264 = new Solution264();
System.out.println(sol264.nthUglyNumber(10));
System.out.println(sol264.nthUglyNumber_2(10));