面试题49:丑数。我们把只包含因子2、3、5的数称为丑数。求按从小到大的顺序的第1500个丑数。如6、8是丑数,但14不是,因为14包含因子7,习惯上我们把1当做第一个丑数。
根据丑数定义,丑数只能被2、3、5整除,即,如一个数能被2(3或5)整除,就连续除以2(3或5),如果最后得到的是1,那么这个数就是丑数,否则不是:
bool isUgly(int num) {
while (num % 2 == 0) {
num /= 2;
}
while (num % 3 == 0) {
num /= 3;
}
while (num % 5 == 0) {
num /= 5;
}
return (num == 1) ? true : false;
}
然后对每个数字计算,但这种方法时间效率不高,是不是丑数都要进行计算。
我们可以只对丑数进行计算,根据丑数定义,丑数应该是另一个丑数乘2、3或5的结果(1除外),因此我们可以创建一个数组,里面都是排好序的丑数,每个丑数都是前面丑数乘2、3或5得到的。
这种思路关键在于如何确保数组里的丑数都是排好序的,假设数组中已经有若干个排好序的丑数,并且把已有最大丑数记作M,下一个丑数一定是前面某一个丑数乘2、3或5的结果,我们首先把已有丑数乘2,我们会得到若干个小于M的结果,由于是按顺序生成的,因此小于等于M的丑数一定已经在数组里了,我们不需再考虑,另外还会得到若干个大于M的结果,但我们只需要取第一个大于M的结果即可,记为M2。以同样结果我们得到M3和M5,然后比较这三个数,找到最小的即为下一个丑数。
前面分析时,每次都从头到尾找到M2、M3、M5,这是不必要的,我们只需要把求出M2的这个位置T2记下来,下次继续从此处开始即可:
#include <iostream>
#include <vector>
using namespace std;
int GetUglyNum(int index) {
if (index < 1) {
return 0;
}
vector<int> uglys = { 1 };
int uglyNum = 1;
int T2 = 0, T3 = 0, T5 = 0;
while (uglyNum < index) {
int minT = min(min(uglys[T2] * 2, uglys[T3] * 3), uglys[T5] * 5); // 找到比当前最大的丑数后面的丑数
uglys.push_back(minT);
while (uglys[T2] * 2 <= uglys[uglyNum]) { // 当<=时,说明uglys里已经有这个数了
++T2;
}
while (uglys[T3] * 3 <= uglys[uglyNum]) {
++T3;
}
while (uglys[T5] * 5 <= uglys[uglyNum]) {
++T5;
}
++uglyNum;
}
int res = uglys[uglyNum - 1];
return res;
}
int main() {
cout << GetUglyNum(1500) << endl; // 打印859963392
}
第二种思路不需要在非丑数的整数上进行任何计算,但需要保存已经生成的丑数,因此需要一个数组,来容纳1500个丑数,占用1500x4字节的内存,相当于用空间换时间。