剑指Offer——丑数

题目描述

把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。

方法一

首先想到的是如果一个数是丑数,那么它应该可以分解成一个丑数与2或3或5的积,所以可以将已经得到的丑数进行存储,如果i满足下述的条件,则i是丑数:

  • i能够整除2且i/2也是丑数
  • i能够整除3且i/3也是丑数
  • i能够整除5且i/5也是丑数

所以采用vector<int> dp,如果dp[i]==1表示i是丑数,否则表示i不是丑数。

代码如下:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if( index <= 6 )
            return index;
        vector<int> dp;
        for( int i=0;i<=6;i++ )
            dp.push_back(1);
        int count = 6;
        int cur = 7;
        while( count < index )
        {
            if( ( cur%5==0 && dp[cur/5]==1 ) || ( cur%3==0 && dp[cur/3]==1 ) || ( cur%2==0 && dp[cur/2]==1 ) )
            {
                dp.push_back(1);
                count++;
            }
            else
                dp.push_back(0);
            cur++;
        }
        return cur-1;
    }
};

缺点:空间复杂度过大,第1500个丑数的大小为859963392,就需要859963392这么大的空间,开销太大

方法二

因为方法一提交后会报出内存超过限制的错误,所以改用了unordered_set<int> s来存储已知的丑数,用s.find(i)来判断i是否已经存储在s内从而判断i是否为丑数,减少了空间复杂度,代码如下:

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if( index <= 6 )
            return index;
        unordered_set<int> s;
        for( int i=0;i<=6;i++ )
            s.insert(i);
        int count = 6;
        int cur = 7;
        while( count < index )
        {
            if( ( cur%2==0 && s.find(cur/2)!=s.end() ) || ( cur%3==0 && s.find(cur/3)!=s.end() ) || ( cur%5==0 && s.find(cur/5)!=s.end() ) )
            {
                s.insert(cur);
                count++;
            }
            cur++;
        }
        return cur-1;
    }
};

这个方法虽然把空间复杂度降下来了,但是时间复杂度更高了,所以会超时

方法三

根据上面说的,一个丑数应该是由另外一个更小的丑数乘以2或3或5得到的,那么可以换种思路,如果已知一部分丑数,其中最大的丑数M,如何找到M的下一个丑数呢?

因为一个丑数应该是由另外一个更小的丑数乘以2或3或5得到的,所以可以对已知的丑数(排好序的)全部乘以2、3、5,选其中最小的一个,就是M的下一个丑数。因为需要找到的是大于M的最小的丑数,所以在对已知的丑数乘以2、3、5的过程中,不需要所有的已知的丑数都求一遍,只要的到大于M的数,更大的丑数就不用去乘了。

此外,还有一个优化就是,每次对已知的丑数乘以2、3、5的过程,也不需要从头开始,只要从上次搜索的结束位置开始即可。

举例:现在已知的排好序的丑数为:1 2 3 4 5 6,那么已知的最大丑数M=6。然后将已知的丑数分别与2、3、5相乘:

  • 与2相乘得到2 4 6 8,此时8已经大于M,后面的数就可以不再计算,记录下min2=8,以及此时与2相乘的4在序列中的位置i2=3;
  • 与3相乘得到3 6 9,此时9已经大于M,后面的数就可以不再计算,记录下min3=9,以及此时与3相乘的3在序列中的位置i3=2;
  • 与5相乘得到5 10,此时10已经大于M,后面的数就可以不再计算,记录下min5=10,以及此时与5相乘的2在序列中的位置i5=1;

那么,M的下一个值应该为min2,min3和min5中的最小值,即8,将8加入到序列中。

继续搜索8的下一个丑数时,与2相乘的值可以从i2+1的位置开始,而与3相乘的值则应该从i3开始,与5相乘的值应该从i5位置开始。

class Solution {
public:
    int GetUglyNumber_Solution(int index) {
        if( index <= 6 )
            return index;
        vector<int> res(index+1, 0);
        for( int i=1;i<=6;i++ )
            res[i] = i;
        int i1=1, i2=1, i3=1;//用于记录上一次的位置
        for( int i=7;i<=index;i++ )
        {
            //每个数乘以2
            int min2 = 0; //记录乘以2得到的第一个大于M的数
            while( min2 <= res[i-1] )
            {
                min2 = res[i1]*2;
                i1++;
            }
            //每个数乘以3
            int min3 = 0;//记录乘以3得到的第一个大于M的数
            while( min3 <= res[i-1] )
            {
                min3 = res[i2]*3;
                i2++;
            }
            //每个数乘以5
            int min5 = 0;//记录乘以5得到的第一个大于M的数
            while( min5 <= res[i-1] )
            {
                min5 = res[i3]*5;
                i3++;
            }
            res[i] = min( min2, min(min3,min5) );
            if( res[i] == min2 )
            {
                i2--;
                i3--;
            }
            if( res[i] == min3 )
            {
                i1--;
                i3--;
            }
            if( res[i] == min5 )
            {
                i1--;
                i2--;
            }
        }

        return res[index];
    }
};

这样时间复杂度和空间复杂度就都可以满足要求了。

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值