leetcode——丑数相关习题

本文深入探讨了计算机科学中的‘丑数’概念,包括如何判断一个数是否为丑数,以及如何找到第n个由特定质因数(2, 3, 5)组成的丑数。通过介绍堆和动态规划两种方法解决‘丑数II’问题,展示了算法在解决数学问题上的应用。同时,文章还讨论了‘超级丑数’和‘丑数III’的解题思路,对于后者,提出了利用集合论和二分查找优化解决方案。
摘要由CSDN通过智能技术生成

相关题目

  1. 263.丑数
  2. 264.丑数II
  3. 剑指 Offer 49. 丑数
    (这道题的内容和上面的丑数II相同)
  4. 313.超级丑数
  5. 1201.丑数III

解答

丑数

这道题是判断一个数字是否为丑数,也就是只由235构成的数。
想法也比较简单,我们只需要不断剥离这些因子,最终得到1就说明为丑数。
代码:

def isUgly(self, n: int) -> bool:
        if n <= 0:
            return False
        while n%2 == 0:
            n = n>>1
        while n%3 == 0:
            n /= 3
        while n%5 == 0:
            n /= 5
        return n ==1

因为2、3、5这些数不相关,所以没有除的先后之分。

丑数II

给出第n个由2、3、5构成的丑数,这个题就开始不对劲了。
首先,这个题我们是没法遍历每一个数字,然后判断是否为丑数,计数器自增的,(从1开始,如果是丑数i++,直到为n),因为n最大1690,估计应该是int型的最大范围了,直接超时。
那么我们就只能构造丑数了,根据题目,丑数是由2、3、5构成的,那么首先[1, 2, 3, 5]这些一定是丑数(1是题目限定的),然后[4, 6, 10]和[6, 9, 15]这些衍生出来的丑数就也是了。
其实到这里,就有想法了,我们要自己构造丑数,这里给出两个方法,堆和dp。

堆:
由于新的丑数是根据之前的丑数来的,那么我们就将[1,2,3,5]这些初始丑数放入一个堆,每一次选出最小的元素,乘以[2,3,5]后得到新的一匹丑数,放入堆中,直到我们取出第n个根。
问题在于,我们难免构造出相同的丑数,这里的建议的使用哈希表。

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        h = [1,2,3,5]
        # 构造小顶堆
        heapq.heapify(h)
        num_set = set(h)
        for _ in range(n):
            min_num = heapq.heappop(h)
            if min_num*2 not in num_set:
                num_set.add(min_num*2)
                heapq.heappush(h,min_num*2)
            if min_num*3 not in num_set:
                num_set.add(min_num*3)
                heapq.heappush(h,min_num*3)
            if min_num*5 not in num_set:
                num_set.add(min_num*5)
                heapq.heappush(h,min_num*5)
        return min_num

有一说一,空间上确实会大很多。

dp:
这里的dp,也是采用构造丑数的方式,这个方法也可以叫做3指针。

在堆中,有一个问题就是,最开始形成的是[2,3,5]一组丑数,添加到堆中,然后又pop出2,添加了新的一匹丑数[4,6,10],
此时堆中剩下的是[3,4,5,6,10],然后拿出3,拿出4,然后拿出5。(宁搁着搁着呢?)
别,你看,我最开始就把5丢进去了,结果跑了好几次才轮到他,就一直放在堆里面能不占地方吗。

那我就不能等等把5放进去?因为新丑数都是根据之前的丑数来的,如果我发现当前5乘以丑数x还放不进去,下一次放入就还是5x,在x之后pop的数据根本不用看。
这样其实就不存在放入了,因为一共就三个数(2、3、5分别对应一个丑数),新的最小丑数一定在他们中间,直接选择即可。

所以我们可以这样,将所有pop出的数据放在一起呈一个数组,并对2、3、5分别给一个指针,指向数组中的一个下标,这样就能产生新的三个丑数,选择最小的元素,就是下一个丑数,同时在选择后将指针后移即可。

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        ret = [0]*n
        ret[0] = 1
        p_two , p_three, p_five = 0, 0, 0
        for i in range(1, n):
        	# 选择三个数中最小的作为新丑数
            ret[i] = min(ret[p_two]<<1, (ret[p_three]<<1)+ret[p_three], (ret[p_five]<<2)+ret[p_five])
            if ret[i] == ret[p_two]<<1:
                p_two += 1
            if ret[i] == (ret[p_three]<<1)+ret[p_three]:
                p_three += 1
            if ret[i] == (ret[p_five]<<2)+ret[p_five]:
                p_five += 1
        return ret[-1]

可能会注意到一个问题,那就是为什么我没有用if-else,而是并列是三个if,和上面的一样,难免产生相同的丑数,这时我们只需要取一个数,但是多个指针都需要移动,防止产生相同的数

超级丑数

这个和丑数II基本上一样,所以不做太多的说明,也就是将3指针换成k指针。

class Solution:
    def nthSuperUglyNumber(self, n: int, primes: List[int]) -> int:
        ret = [0]*n
        ret[0] = 1
        ls = [0]*len(primes)    # 索引
        res = primes[:]         # 乘积
        for i in range(1, n):
            ret[i] = min(res)
            for j in range(len(primes)):
                if res[j]==ret[i]:
                    ls[j] += 1
                    res[j] = ret[ls[j]]*primes[j]
        # print(ret)
        return ret[-1]
丑数III

这题,其实一看还是之前的思路,但是真要用之前的那个,不管是dp还是堆,你看第二个示例和他数据范围,直接当场去世。
这题,我们还要换一个思路:
前面的题,我们发现其实丑数就是用你给的丑数去乘得到的,那么对于一个数,我们能否判断他是第几个丑数呢?
能!
我们想一下,对于一个数x,他前面的数无非也就是由a、b、c相乘得到的,那么我们只要计算x前面有多少个含有a、b和c的数,加起来是不是就是前面有多少个元素了?

我们看一下数35,其中a=3,b=5,c=7:
a:3、6、9、12、15、18、21、24、27、30、33
b:5、10、15、20、25、30、35
c:7、14、21、28、35
很明显出现了重复,这里我们就需要舍弃这些重复的。我第一反应也是不会,但是请看这个:
在这里插入图片描述
集合论还不清楚?
然后我们再丢一个公式:x//a + x//b + x//c - x//lcm(a,b) - x//lcm(a,c) - x//lcm(b,c) + x//lcm(lcm(a,b),c)
(其中lcm是计算最小公倍数)

但对于一个数,我们根据当前的方式,第n个丑数其实是有很多个的,他们都能根据公式计算出前面有n-1个丑数,这时我们就要想明白,第n个丑数是这些丑数中最小的!

现在我们就能知道一个数可能是第几个丑数了,另一个问题就是我们对于这么大的一个数集,其实能采用的也就是二分法了。
下限为min(a,b,c),上限本来应该是max(a,b,c)n,但n过大实际上是不可行的,我们使用的是给出的返回值最大值2000000009。

def nthUglyNumber(n, a, b, c):
    def get_pos(x):
        def lcm(x,y):
            # 返回最小公倍数
            m = max(x, y)
            n = min(x, y)
            while m%n:
                m, n = n, m%n
            return x*y//n
        return x//a + x//b + x//c - x//lcm(a,b) - x//lcm(a,c) - x//lcm(b,c) + x//lcm(lcm(a,b),c)
    l, r = min(a,b,c), 2000000009
    while l < r:
        mid = (l+r)>>1
        #print(mid)
        if get_pos(mid) < n:
            l = mid+1
        else:
            r = mid
    return l
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值