把只包含因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
代码
解法一
所谓一个数m是另一个数n的因子,是指n能被m整除,也就是说n%m==0.
public static int[] findUglyNumbers(int n) {
if (n <= 0) {
return null;
}
int[] result = new int[n];
result[0] = 1;
int index = 1;
int calNumber = 1;
while (index < n) {
calNumber++;
// 是不是丑数都要计算一次
if (isUglyNumber(calNumber)) {
result[index] = calNumber;
index++;
}
}
return result;
}
/**
* 判断一个数是否是丑数,即只包含因子2,3,5的数,即
* 如果可以被2整除,就连续除以2,直到不能被2整除为止
* 如果可以被3整除,就连续除以3,直到不能被3整除为止
* 如果可以被5整除,就连续除以5,直到不能被5整除为止
* 如果最后的结果是1,说明是丑数,否则不是
* 例如,30,可以被2整除,然后除以2得到15
* 15,可以被3整除,然后除以3得到5
* 5,可以被5整除,然后除以5得到1
* @param n
* @return
*/
private static boolean isUglyNumber(int n) {
while (n % 2 == 0) {
n /= 2;
}
while (n % 3 == 0) {
n /= 3;
}
while (n % 5 == 0) {
n /= 5;
}
return n == 1;
}
解法二
只计算丑数,而不计算非丑数。丑数=丑数*(2/3/5),所以创建数组保存有序的丑数。关键在于如何在计算丑数的过程中保持数组有序。当前的丑数必然是之前某一个丑数*因子的结果,但是不需要每个数都要乘一遍2、3、5。要获得的丑数必然是大于现在已有的,在计算得出丑数中选择一个最小的放入数组中,来保持数组的有序,因为新放入的丑数是根据之前的丑数计算得到的,所以必然是有序的。为了每次新得到的三个丑数都是比已有丑数大,且最小,所以要记录各个因子下次计算要使用的已有丑数在什么位置,否则就会出现跳数,比如已有{1,2,3,4},我们知道下一个丑数应该是5,但是如果因子5没有选择第一个丑数1来相乘,就会漏掉5这个丑数。
public static int[] findUglyNumbers2(int n) {
if (n <= 0) {
return null;
}
int[] result = new int[n];
// 1作为第一个丑数
result[0] = 1;
// 初始2,3,5因子对应的乘数为第一个丑数,即1
int divisor2 = 0;
int divisor3 = 0;
int divisor5 = 0;
// result={1}
// result[divisor2=0]=1, result[divisor3=0]=1, result[divisor5=0]=1, multiply2=2, multiply3=3, multiply5=5
// min = multiply2,所以 此时divisor2=1, divisor3=0, divisor5=0
// result={1, 2}
// result[divisor2=1]=2, result[divisor3=0]=1, result[divisor5=0]=1, multiply2=4, multiply3=3, multiply5=5
// min = multiply3,所以 此时divisor2=1, divisor3=1, divisor5=0
// result={1, 2, 3}
// result[divisor2=1]=2, result[divisor3=1]=2, result[divisor5=0]=1, multiply2=4, multiply3=6, multiply5=5
// min = multiply2,所以 此时divisor2=3, divisor3=1, divisor5=0
// result={1, 2, 3, 4}
// result[divisor2=3]=4, result[divisor3=1]=2, result[divisor5=0]=1, multiply2=8, multiply3=6, multiply5=5
// min = multiply5,所以 此时divisor2=3, divisor3=2, divisor5=1
// result={1, 2, 3, 4, 5}
// result[divisor2=3]=4, result[divisor3=2]=3, result[divisor5=1]=2, multiply2=8, multiply3=6, multiply5=10
// min = multiply3,所以 此时divisor2=3, divisor3=3, divisor5=1
// result={1, 2, 3, 4, 5, 6}
for (int i = 1; i < n ; i++) {
// 根据因子对应的乘数下表计算得出下一个丑数
int multiply2 = result[divisor2] * 2;
int multiply3 = result[divisor3] * 3;
int multiply5 = result[divisor5] * 5;
// 找出三个因子乘以上一个对应丑数后最小的那个丑数,以此来保证丑数有序
int min = Math.min(multiply2, Math.min(multiply3, multiply5));
// 将最小的丑数放入结果集中,用于下一次计算
result[i] = min;
// 找出对应此次计算最小丑数的因子,并移动指针指向下一次计算薪丑数对应的老丑数下标
if (multiply2 == result[i]) {
divisor2++;
}
if (multiply3 == result[i]) {
divisor3++;
}
if (multiply5 == result[i]) {
divisor5++;
}
}
return result;
}