题目来自http://acm.pku.edu.cn/JudgeOnline/problem?id=1338,我做了一点小改动。
丑陋数是指质因数只包含2,3,5的自然数,例如前十个丑陋数依次为:1, 2, 3, 4, 5, 6, 8, 9, 10, 12。
给定一个自然数n (n <= 1500),请你求出对应的第n个丑陋数。
函数接口:unsigned long UglyNumber(int n);
输入输出示例:
n = 2, 返回 2; n = 5, 返回 5;n = 7, 返回 8; n = 11, 返回 15;
解决此问题的一种思路就是先建立一个丑陋数表,把前1500个丑陋数全部求出来存储到数组lib[]中,这样只要返回lib[n-1]就好了。
本文分析一些建立丑陋数表的方法。
方法一:很容易想到判断一个数是否为丑陋数的方法,那就是判断它的因子是否只包含2,3,5。
最简单的方法就是依次判断各个自然数,直到找齐1500个丑陋数为止。
代码如下:
#define MAX 500
unsigned long UglyNumber(int n)
{
unsigned long lib[MAX] = {1,};//用来存储丑陋数表
unsigned long i, num;
int k = 1;
for (i=2; k<MAX; n++)
{
num = i;
while (num % 2 == 0)
num /= 2;
while (num % 3 == 0)
num /= 3;
while (num % 5 == 0)
num /= 5;
if (num == 1) //i是丑陋数
lib[k++] = i;
}
return lib[n-1];
}
程序很简洁的,也能得到正确的结果。但是当MAX大于1000的时候,程序就会超时。
原因是要逐个检查i,还要进行多次的求余运算,时间复杂度O(nlogn),当i很大时,耗时多。
方法二:利用任何一个丑陋数都是由比它小的丑陋数乘以2或3或5得到,可以利用已经求得的丑陋数表来判断一个数是否为丑陋数。
代码如下:
#define MAX 500
inline bool Find(const unsigned long lib[], int len, unsigned long data);
unsigned long UglyNumber(int n)
{
unsigned long lib[MAX] = {1,2,3,4,5,};
int k = 5;
unsigned long i;
for (i=6; k<MAX; i++)
{
if (i % 2 == 0 && Find(lib, k, i/2))//i能被2整除,且i/2是丑陋数
lib[k++] = i;
else if (i % 3 == 0 && Find(lib, k, i/3))//i能被3整除,且i/3是丑陋数
lib[k++] = i;
else if (i % 5 == 0 && Find(lib, k, i/5))//i能被5整除,且i/5是丑陋数
lib[k++] = i;
}
return lib[n-1];
}
inline bool Find(const unsigned long lib[], int len, unsigned long data)//判断data是否为lib[]的元素
{
int i;
for (i=0; i<len && data>=lib[i]; i++)
{
if (data == lib[i])
return true;
}
return false;
}
此方法看似比方法一要快,实际上因为每次都要遍历数组lib[],所花时间比求余运算还多。时间复杂度为O(i*k),比方法一还要高。
看来,计算的瓶颈在于需要逐个判断自然数,当i的值很大时,耗时太多。能否不用排除法,而直接求出丑陋数表呢?我陷入了沉思。
方法三:为了便于分析,我把前50个丑陋数列出来。很快,我发现了规律:
观察数列:1
2 3 5
4 6 9 10 15 25
8 12 18 27 20 30 45 50 75 125
16 24 36 54 81 40 60 90 135 100 150 225 250 375 625
32 48 72 108 162 243 80 120 180。。。
可以看出每行依次递增3,4, 5,6。。。个元素。以第2行为例,递增的元素分别是
4 = 2 * 2, 6 = 3 * 2, 9 = 3 * 3,后面三个元素10,15,25分别由2,3,5各乘以5得到;
以第3行为例,递增的元素分别是
8 = 4 * 2, 12 = 6 * 2, 18 = 9 * 2,27 = 9 * 3;
后面六个元素20 30 45 50 75 125分别由4 6 9 10 15 25各乘以5得到;
由此可以得出规律:
设k表示栈顶,由于数组栈已经存储了4个元素{1,2,3,5,},则k初始值为4。
设第n(n>1)行最左边元素的下标为left,最右边元素的下标为right-1,sum表示第n行的元素个数,其中n初始值为2,sum初始值为1。则对于第n行(n>1)来说,
sum += n; left = k - sum; right = k;
根据第n行的元素值,求出第n+1行的各个元素。
其中前n-1个元素为 for (i=left; i<left+n; i++)
lib[k++] = lib[i] * 2;
第n个元素为 lib[k++] = lib[left+n-1] * 3;
后面的sum个元素为 for (; left<right; left++)
lib[k++] = lib[left] * 5;
依此类推,直到存储了足够的元素。
注意:此处不能以k==MAX为结束标志,因为数组的元素不是递增排列的,有可能遗漏前面较小的元素。
解决方案是宁多勿缺,增大k的取值,经测试当MAX=1500时,取MAX*9较好,注意同时设置lib[MAX*10];
为防止溢出,把溢出的数值设置为0,注意即便数值很大,发生溢出,也不能跳出循环,因为k值必须递增,以满足前面总结出来的规律。
最后对得到的数组按递增排序,消去前面为0的元素。
这样得到的丑陋数虽然远大于1500个,但因为时间复杂度不高,反而非常快,是典型的用空间换时间方法。
代码如下:
#define MAX 1500
int Move(unsigned long lib[], int len);
static int Compare(const void *p1, const void *p2);
unsigned long UglyNumber(int n)
{
unsigned long lib[MAX*10] = {1,2,3,5,};
int left, right;
int k = 4;
int n = 2;
int sum = 1;
unsigned long max = MAX*9;
while (k < max)
{
sum += n;
left = k - sum;
right = k;
int i;
for (i=left; i<left+n; i++)
{
if (lib[i] * 2 < LONG_MAX) //若数值太大(溢出),则取0
lib[k++] = lib[i] * 2;
else
lib[k++] = 0;
}
if (lib[left+n-1] * 3 < LONG_MAX) //若数值太大(溢出),则取0
lib[k++] = lib[left+n-1] * 3;
else
lib[k++] = 0;
for (; left<right; left++) //若数值太大(溢出),则取0
{
if (lib[left] * 5 < LONG_MAX)
lib[k++] = lib[left] * 5;
else
lib[k++] = 0;
}
n++;
}
qsort(lib, k, sizeof(unsigned long), Compare);//递增排列
k = Move(lib, k); //去掉数值为0的元素
return lib[n-1];
}
static int Compare(const void *p1, const void *p2)
{
return (*(unsigned long *)p1) - (*(unsigned long *)p2);
}
int Move(unsigned long lib[], int len)
{
int i = 0;
while (lib[i] == 0)
i++;
int j = 0;
while (i < len)
lib[j++] = lib[i++];
return j;
}
正文
用空间换时间---对处理大量数据问题的一点思考2006-11-11 16:36:00
【评论】 【打印】 【字体:大 中 小】 本文链接:http://blog.pfan.cn/goal00001111/20204.html
阅读(2548) | 评论(0)
版权声明:编程爱好者网站为此博客服务提供商,如本文牵涉到版权问题,编程爱好者网站不承担相关责任,如有版权问题请直接与本文作者联系解决。谢谢!
评论