题目描述
编写一个程序,找出第 n 个丑数。
丑数就是质因数只包含 2, 3, 5
的正整数。
思考
我们要找第n
位的丑数,那就是把丑数从小到大排序,取出其中第 n
大的数即是我们想要的结果。那么现在的问题就是我们怎么去找第 n
大的这个数。
因为丑数是只包含 2、3、5
的正整数。因此我们需要排序的其实是 2、3、5
的倍数们。
方法一 (这是我第一次的想法,笨办法,稍显奇葩)
我们已1
作为根节点,分别乘以 2,3,5
。得到第一层数据,再分别乘以 2,3,5
得到第二层数据。
这时候我们发现两个问题,
1、下一层的数据不一定比上一层小; 2、一层的数据有可能有重叠。
只要解决这两个问题,我们就可以保证每一次压入丑数数组的数据是最小的。
我们先声明一个数组 temp
作为数据池,存储那些已经计算出来,但是并非最小的丑数。
针对第一个问题,我们可以在每一次将最小数压入数组后,将其乘以 2,3,5
。并放到 temp
数据池中,然后遍历数据池中元素,取出最小,压入丑数数组,在数据池中删除该数。重复上述过程即可。
第二个问题
第二个问题产生的原因是基于乘法交换律产生的,因此我们可以对 temp
数据池中的数字做一个标记,记录它们最后一个乘数是谁 lastMul
。 然后在 2,3,5
中选择小于 lastMul
的乘数即可。
代码如下
let uglyNum = [1];
let tempMap = new Map(); // 临时数据存储区
tempMap.set(2, 2);
tempMap.set(3, 3);
tempMap.set(5, 5);
let mulArr = [2, 3, 5];
nextUgly(n);
return uglyNum[n - 1];
/*
{
value: // 当前值
lastMul: // 最后乘数
}
*/
function nextUgly(n) {
if (uglyNum.length >= n) {
return;
}
let minVal = -1;
for (let [value, lastMul] of tempMap) {
if (minVal == -1 || minVal > value) {
minVal = value
}
}
uglyNum.push(minVal);
let lastMul = tempMap.get(minVal);
tempMap.delete(minVal);
for (let i = 0; i < mulArr.length; i++) {
if (lastMul >= mulArr[i]) {
tempMap.set(minVal * mulArr[i], mulArr[i]);
}
}
nextUgly(n);
}
复制代码
方法二 (动态规划)
这个是基于前面的笨办法想到的。 因为丑数数组中的每一个数其实都是基于前面的元素乘以 2,3,5
产生的。因此我们可以用三个标记 i2,i3,i5
作为标记。比较 uglyNum[i2] * 2
, uglyNum[i3] * 3
, uglyNum[i5] * 5
的最小值, 比如说是 uglyNum[i5] * 5
, 我们将 uglyNum[i5] * 5
压入uglyNum
中,并将 i5
加一。代表这个数已经乘过5了,并且乘积已经被放入丑数数组了,我们现在移向下一个丑数,计算它和5的乘积。
uglyNum[i] = min(uglyNum[i2] * 2, uglyNum[i3] * 3, uglyNum[i5] * 5);
复制代码
代码如下
let uglyNum = [1];
let hasNum = new Set();
hasNum.add(1);
nthNum(n, 0, 0, 0);
function nthNum(n, i2, i3, i5) {
if (uglyNum.length >= n) {
return;
}
let tempN2 = uglyNum[i2] * 2;
let tempN3 = uglyNum[i3] * 3;
let tempN5 = uglyNum[i5] * 5;
let minNum = Math.min(Math.min(tempN2, tempN3), tempN5);
if (minNum === tempN2) {
i2++;
} else if (minNum === tempN3) {
i3++;
} else if (minNum === tempN5) {
i5++;
}
if (!hasNum.has(minNum)) {
uglyNum.push(minNum);
hasNum.add(minNum);
}
nthNum(n, i2, i3, i5);
}
return uglyNum[n-1];
复制代码