Ugly Number I II 解析

leetcode上关于ugly number(丑数)的题目有两个,先说一下什么叫丑数,很多博客上关于丑数的解释比较模糊,有的解释成能被2,3,5整除的数,这算是一条丑数的性质但是表达的有些歧义,比如14,35明明可以被2和5整除却不是丑数。其实丑数是一种能写成2,3,5的乘方组合(number == 2^m*3^n*5^k,m,n,k为自然数)的形式的数字,比如180这个数字,可以写成2*2*3*3*5,它不仅可以被2,3,5整除,而且可以循环整除到1。

其实理解了丑数后,如何判断一个数是不是丑数的方法就可以根据这条性质来判断,代码实现如下所示:

class Solution {
public:
    bool isUgly(int num)
    {
        if(num == 0)
            return false;
        while(num % 2 == 0)//循环除去2的乘方
            num /= 2;
        while(num % 3 == 0)//循环除去3的乘方
            num /= 3;
        while(num % 5 == 0)//循环除去5的乘方
            num /= 5;
        return num == 1;//最后的结果如果等于1,就代表是丑数了
    }
};
接下来是难度较大的得到从1开始数的第n个丑数,

方法一:利用上面的判断一个数字是不是丑数,设置一个计数器i,对整个自然数列从1开始遍历,如果isUgly(number)返回true的话 i++;当i == n的时候就得到了第n个丑数。这个方法实现简单,但是效率很低,因为每一个数字都要判断一下他是不是丑数。

方法二:还是从丑数的特性出发来看这个问题,既然是2,3,5的乘方组合,那么按序排列的丑数列,后面的丑数都可以由前面的某一个丑数乘以2或者3或者5得到。看个例子来讲:

丑数列:1,2,3,4,5,6,8,9,10,12中8可以由4*2得到,9可以由3*3得到,10可以由2*5或者5*2得到,12可以由6*2得到也可以由4*3得到,不管一个丑数可以有几种获得方法,都是排在他前面的某一个丑数与2,3,5中的某一个乘积得到的。

那么我们如何得到一个已知的排好序的丑数列的下一个丑数呢,很简单,将丑数列num[n]中所有的元素都分别乘以2得到一个有序数列num2[n],乘以3得到一个有序数列num3[n],乘以5得到一个有序数列num5[n],这三个新数列中肯定有很多元素比num[n-1]大,这三个新数列中所有的比num[n-1]大的元素里最小的一个就是我们要找的num的下一个元素。同样的举个例子来说:

丑数列*1:1,2,3,4 

丑数列*2:2,4,6,8

丑数列*3:3,6,9,12

丑数列*5:5,10,15,20

得到的三个数列中比4大的元素都上颜色了,这些元素里面最小的是5,所以得到丑数列:1,2,3,4,5。解释到这里,解题的主要思想已经有了,接下来就是优化的问题。

首先,这三个数列有没有必要存在实体?还是用上面那个例子,这三个数列是可以用原数列乘以2,3,5得到的,因为原数列有序,所以新数列也有序,但是我们最关心的不是三个完整的数列,而是每个数列中最小的那个比4大的元素,也就是标绿的元素,而标红的元素就算是比4大也可以直接忽略(因为是有序的嘛,最小值肯定不是红色的元素),而比4大的下一个丑数就是绿色的三个元素里面的最小值,所以只要每次将原数列的所有元素乘以2,记录得到的第一个比4大的元素,乘以3记录第一个比4大的元素,乘以5记录第一个比4大的元素,最后比较记录下的三个元素,就是比4大的下一个丑数。

其次,是不是每次都需要从1开始算元素乘以2,乘以3,乘以5的结果呢?算4的下一个丑数时,对于乘以2得到的数列,1*2,2*2都已经在上一轮的比较中小于等于4了,所以不需要在求4的下一个丑数时再去计算比较一下,同样的对于乘以3得到的数列,1*3得到的3也不用考虑了,所以,需要有三个计数器来分别记录三个数列中比4小的元素索引,这样在计算5的时候,可以直接从这三个索引的下一个开始计算。

代码实现:

class Solution {
public:
    int nthUglyNumber(int n) 
    {
        if (n == 1)
            return 1;
        int *tmp = new int[n];//记录丑数列
        tmp[0] = 1;
        int m2 = 0, m3 = 0, m5 = 0;//记录三个索引
        for (int j = 1; j < n; j++)
        {
            while (tmp[m2] * 2 <= tmp[j - 1])//找丑数列*2的数列中第一个比tmp[j-1]大的元素索引
                m2++;
            while (tmp[m3] * 3 <= tmp[j - 1])//同上
                m3++;
            while (tmp[m5] * 5 <= tmp[j - 1])//同上
                m5++;
            tmp[j] = minofthree(tmp[m2] * 2,tmp[m3] * 3,tmp[m5] * 5);
        }
        return tmp[n - 1];
    }
    int minofthree(int a,int b,int c)
    {
        return min(min(a,b),c);
    }
};



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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值