7-4 传说中的丑数

7-4 传说中的丑数

质因数是2,3,5,7的数称为丑数(humble number)。1,2,3,4,5,6,7,8,9,10,12,14,15,16,18,20,21,24,25,27是丑数序列的前20个数。请你算一下,上述序列中的第n个元素是几。

输入格式:

输入有多组测试数据,每组测试数据包括一个整数n(1<=n<=5842)。n=0时输入结束。

输出格式:

对于每组测试数据,输出"The nth humble number is x",其中根据n的值,后缀有"st"、“nd”、"rd"或"th"等不同情况,x表示元素值。

输入样例:
1
2
3
4
11
12
13
21
22
23
100
1000
5842
0
输出样例:
The 1st humble number is 1.
The 2nd humble number is 2.
The 3rd humble number is 3.
The 4th humble number is 4.
The 11th humble number is 12.
The 12th humble number is 14.
The 13th humble number is 15.
The 21st humble number is 28.
The 22nd humble number is 30.
The 23rd humble number is 32.
The 100th humble number is 450.
The 1000th humble number is 385875.
The 5842nd humble number is 2000000000.
思路分析:

刚拿到题时,我感觉无处下手,首先什么是丑数?题干叙述的很模糊,只知道质因数为2、3、5、7的数为丑数。其实丑数就是他的能被2、3、5、7四个数中的一个或多个整除或他们的所有因数都能被2、3、5、7四个数中的一个或多个整除,如果不满足上述的两个条件,则该数就不是丑数。
知道什么是丑数后,就需要去考虑怎么得到丑数,如果仅判断丑数,那么就非常容易,只需要逐个去取余2、3、5、7,若得0,则再去除2、3、5、7,反复进行上述两步,最后得1就是丑数
其他的就不是

判断丑数:
bool judge_ugly(int num) {
	if (num < 1) {
		return false;
	} 
	else{
		while (num % 2 == 0){
			num /= 2;
		}
		while (num % 3 == 0){
			num /= 3;
		}
		while (num % 5 == 0){
			num /= 5;
		}
		while (num % 7 == 0){
			num /= 7;
		}
		
		if (num == 1) {
			return true;
		} else {
			return false;
		}
	}
}

本题要求给定一个数字n,返回丑数数列得第n个数,这样我们就需要求出0 - n得所有丑数,如果将0 - n每一位都放入判断丑数函数中进行判断,非常耗时,对本题得边缘样例5842进行测试,这样逐一遍历的方法计算出第5842个丑数需要50s左右,在PTA上一定会报“运行超时”。
此时我们就需要找丑数的规律,一般我们认为1是第一个丑数,接下来会发现除了丑数1、2、3、5、7外,其他的丑数都是2、3、5、7的乘积,丑数 = 2^x * 3^y * 5^z * 7^t,这样我们就可以将所有丑数分成四组, 换句话说一个丑数一定由另一个丑数乘以2或者乘以3或者乘以5或者乘以7得到。那么我们从1开始乘以2、3、5、7,就得到2、3、5、7三个丑数,在从这三个丑数出发乘以2、3、5、7就得到4、6、10、14、6、9、12、21、10、15、25、35、14、21、35、49十六个丑数,我们发现这种方法会得到重复的丑数,而且我们题目要求第N个丑数,这样的方法得到的丑数也是无序的。那么我们可以维护四个队列:
(1)丑数数组: 1

乘以2的队列:2

乘以3的队列:3

乘以5的队列:5

乘以7的队列:7

选择四个队列头最小的数2加入丑数数组,同时将该最小的数乘以2、3、5、7放入三个队列;

(2)丑数数组:1、2

乘以2的队列:4

乘以3的队列:3、6

乘以5的队列:5、10

乘以7的队列:7、14

选择四个队列头最小的数3加入丑数数组,同时将该最小的数乘以2、3、5、7放入三个队列;

(3)丑数数组:1、2、3

乘以2的队列:4、6

乘以3的队列:6、9

乘以5的队列:5、10、15

乘以7的队列:7、14、21

选择四个队列头里最小的数4加入丑数数组,同时将该最小的数乘以2、3、5、7放入三个队列;

(4)丑数数组:1、2、3、4

乘以2的队列:6、8

乘以3的队列:6、9、12

乘以5的队列:5、10、15、20

乘以7的队列:7、14、21、28

选择四个队列头里最小的数5加入丑数数组,同时将该最小的数乘以2,3,5放入三个队列;

(5)丑数数组:1、2、3、4、5

乘以2的队列:6、8、10,

乘以3的队列:6、9、12、15

乘以5的队列:10、15、20、25

乘以7的队列:7、14、21、28、35

选择四个队列头里最小的数6加入丑数数组,但我们发现,有两个队列头都为6,所以我们弹出两个队列头,同时将12、18、30、42放入四个队列;

……………………

疑问:

1.为什么分四个队列?

丑数数组里的数一定是有序的,因为我们是从丑数数组里的数乘以2、3、5、7选出的最小数,一定比以前未乘以2、3、5、7大,同时对于四个队列内部,按先后顺序乘以2、3、5、7分别放入,所以同一个队列内部也是有序的;

2.为什么比较四个队列头部最小的数放入丑数数组?

因为四个队列是有序的,所以取出四个头中最小的,等同于找到了四个队列所有数中最小的。

该思路参考于CSDN博主「JoeJoeTLQ」的原创文章,原文链接:https://blog.csdn.net/JoeJoeTLQ/article/details/84253502

求丑数数列
int Get_Ugly_Number(int index) {
    if (index <= 0) {
        return 0;
    }
    else if (index < 11) {
        return index;
    }
    int result[index] = { 0 };
    //int result[5842] = { 0 }   //在vc环境下声明数组长度时不能使用变量,就用本题给的最大长度声明了
    result[0] = 1;//1是一个特殊的丑数
    int r2 = 0, r3 = 0, r5 = 0, r7 = 0;
    for (int i = 1; i < index; i++) {
        result[i] = min(result[r2] * 2, min(result[r3] * 3, min(result[r5] * 5, result[r7] * 7)));
        if (result[i] == (result[r2] * 2)) {
            r2++;
        }
        if (result[i] == (result[r3] * 3)) {
            r3++;
        }
        if (result[i] == (result[r5] * 5)) {
            r5++;
        }
        if (result[i] == (result[r7] * 7)) {
            r7++;
        }
    }
    return result[index - 1];
}

int min(int a, int b) {
    return a > b ? b : a;
}

求得丑数序列后我们就可以根据输入的n返回对应的丑数,在输出时需要注意题目要求的输出格式: 输出"The nth humble number is x",其中根据n的值,后缀有"st"、“nd”、“rd"或"th"等不同情况,x表示元素值。
后缀有"st”、“nd”、"rd"或"th"其实就是英语中的序数词缩写,在英语语法中,有:

1~30序数词
1-10:first、second、third、fourth、fifth、sixth、seventh、eighth、ninth、tenth
11-20:eleventh、twelfth、thirteenth、fourteenth、fifteenth、sixteenth、seventeenth、eighteenth、nineteenth、twentieth
21-30:twenty-first、twenty-second、twenty-third、twenty-fourth、twenty-fifth、twenty-sixth、twenty-seventh、twenty-eighth、twenty-ninth、thirtieth。
其中1st,2nd,3rd为特殊形式,其它的都是阿拉伯数字后加th。(以1,2,3结尾是st,nd,rd。 eg:twenty-first----21st、thirty-four----34th)

这里我们还需要写一个方法来判断输出时应输出的后缀:
int ordinal(int num)
{
    int sum = 4;
    if (num >= 11 && num <= 20)//11-20全是th 
    {
        return sum;
    }
    else
    {
        int ge_wei_shu = num % 10;//取余10得到个位数的值 
        if (ge_wei_shu > 3 || ge_wei_shu == 0)//0、4-9都是th 
        {
            return sum;
        }
        else
        {
            switch (ge_wei_shu)
            {
            case 1:
                sum = 1;
                break;
            case 2:
                sum = 2;
                break;
            case 3:
                sum = 3;
                break;
            };
            return sum;
        }
    }
}
参考代码:
#include<iostream>
using namespace std;

int Get_Ugly_Number(int index) {
    if (index <= 0) {
        return 0;
    }
    else if (index < 11) {
        return index;
    }
    int result[6000] = { 0 };
    //int result[5842] = { 0 }   //在vc环境下声明数组长度时不能使用变量,就用本题给的最大长度声明了
    result[0] = 1;//1是一个特殊的丑数
    int r2 = 0, r3 = 0, r5 = 0, r7 = 0;
    for (int i = 1; i < index; i++) {
        result[i] = min(result[r2] * 2, min(result[r3] * 3, min(result[r5] * 5, result[r7] * 7)));
        if (result[i] == (result[r2] * 2)) {
            r2++;
        }
        if (result[i] == (result[r3] * 3)) {
            r3++;
        }
        if (result[i] == (result[r5] * 5)) {
            r5++;
        }
        if (result[i] == (result[r7] * 7)) {
            r7++;
        }
    }
    return result[index - 1];
}

int min(int a, int b) {
    return a > b ? b : a;
}

int ordinal(int num)
{
    int sum = 4;
    if (num >= 11 && num <= 20)//11-20全是th 
    {
        return sum;
    }
    else
    {
        int ge_wei_shu = num % 10;//取余10得到个位数的值 
        if (ge_wei_shu > 3 || ge_wei_shu == 0)//0、4-9都是th 
        {
            return sum;
        }
        else
        {
            switch (ge_wei_shu)
            {
            case 1:
                sum = 1;
                break;
            case 2:
                sum = 2;
                break;
            case 3:
                sum = 3;
                break;
            };
            return sum;
        }
    }
}

void excute()
{
    int group[100] = { 0 }, ugly_num[100] = { 0 };
    int group_count = 0;
    while (cin >> group[group_count])
    {
        if (group[group_count] == 0)
        {
            break;
        }
        ugly_num[group_count] = Get_Ugly_Number(group[group_count]);
        group_count++;
    }
    for (int i=0;i<group_count;i++)
    {
        cout << "The " << group[i];
        switch (ordinal(group[i]))
        {
            case 1:
                cout << "st humble number is " << ugly_num[i] << ".\n";
                break;
            case 2:
                cout << "nd humble number is " << ugly_num[i] << ".\n";
                break;
            case 3:
                cout << "rd humble number is " << ugly_num[i] << ".\n";
                break;
            case 4:
                cout << "th humble number is " << ugly_num[i] << ".\n";
                break;
        }

    }
}
int main()
{
    excute();
    return 0;
}

欢迎交流学习

原创不易,看官点个赞再走~~~

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值