【LEETCODE】313-Super Ugly Number [Python]

Write a program to find the nth super ugly number.

Super ugly numbers are positive numbers whose all prime factors are in the given prime listprimes of size k. For example,[1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] is the sequence of the first 12 super ugly numbers givenprimes = [2, 7, 13, 19] of size 4.

Note:

(1) 1 is a super ugly number for any givenprimes.

(2) The given numbers in primes are in ascending order.

(3) 0 < k ≤ 100, 0 <n ≤ 106, 0 <primes[i] < 1000.


题意:

写个程序找到第n个 super ugly number

super ugly number 就是一些只包含给定的k个质因子的正数

例如 [1, 2, 4, 7, 8, 13, 14, 16, 19, 26, 28, 32] 就是12 个super ugly number构成的序列,它们由 size 为 4 的质数列表 [2, 7, 13, 19]生成 


注:

1 是super ugly number,无论给定怎样的质数列表

给定的质数列表是升序的

0 < k ≤ 100, 0 < n ≤ 106, 0 < primes[i] < 1000


思路:

就是给的size 为 4 的素数列表 [2, 7, 13, 19] 

初始的super ugly number 是1

由1生成的 2,7,13,19: [1,2,7,13,19:]

继续由2及列表生成 [4,14,26,38]

由7及列表生成[14,49,91,133]

由13及列表生成[。。。]

由19及列表生成[。。。]

。。。

sort:以上生成的数字要保持由小到大的顺序---heapq.merge

duplicate:遇到重复的数字只要一个---if ugly != uglies[-1]

N:只返回第N个上述序列中的数字---while len(uglies) < n



参考:

http://segmentfault.com/a/1190000004187449

http://bookshadow.com/weblog/2015/12/03/leetcode-super-ugly-number/


这些数字是怎么产生的

除了第一个数字,其余所有数字都是之前已有数字乘以任意一个在质数数组里的质数

所以对于每一个已有的数字,我们都可以分别乘以所有在质数数组里的质数得到一系列的数字,这些数字肯定会存在在以后的序列中

由于我们是要得到从小到大的结果,所以我们可以维护个count数组

来记录对于对应质数下一个需要被乘的已有数的index



Python:

import heapq

class Solution(object):
    def nthSuperUglyNumber(self, n, primes):
        """
        :type n: int
        :type primes: List[int]
        :rtype: int
        """
        uglies = [1]
        def gen(prime):
            for ugly in uglies:
                yield ugly * prime
        merged = heapq.merge(*map(gen, primes))
        while len(uglies) < n:
            ugly = next(merged)
            if ugly != uglies[-1]:
                uglies.append(ugly)
        return uglies[-1]
        
n=12
primes=[2,7,13,19]
r=Solution().nthSuperUglyNumber(n, primes)
print (r)


不会的地方:

yield:

关于Python中的yield

迭代器:

for循环可用于任何“可迭代对象”,这其实就是迭代器

Python中的迭代器协议就是有next方法的对象会前进到下一结果,而在一系列结果的末尾是,则会引发StopIteration

迭代工具内部会在每次迭代时调用next方法,并且捕捉StopIteration异常来确定何时离开

使用迭代器一个显而易见的好处就是:每次只从对象中读取一条数据,不会造成内存的过大开销。


生成器:

包含yield语句的函数会被特地编译成生成器。当函数被调用时,他们返回一个生成器对象,这个对象支持迭代器接口

不像一般的函数会生成值后退出,生成器函数在生成值后会自动挂起并暂停他们的执行和状态,他的本地变量将保存状态信息,这些信息在函数恢复时将再度有效


>>> def g(n):

...     for iin range(n):

...             yield i**2

...

>>> for iin g(5):

...     print i,":",

...

0 : 1 :4 : 9 :16 :


def gen(prime):
	for ugly in uglies:
		yield ugly*prime

uglies=[1,2,7,13,19]
prime=2

for i in gen(prime):
	print (i)

2

4

14

26

38


用生成器生成一个Fibonacci数列

def fab(max):

    a,b = 0,1

    while a< max:

        yield a

        a, b = b, a+b

 

>>> for iin fab(20):

...     print i,",",

...

0 , 1 ,1 , 2 ,3 , 5 ,8 , 13 ,



map(function, list):


Python中map()函数浅析

>>>defadd100(x):
...    returnx+100
...
>>> hh =[11,22,33]
>>>map(add100,hh)
[111,122,133]

>>>defabc(a, b, c):
...    returna*10000+ b*100+ c
...
>>> list1 =[11,22,33]
>>> list2 =[44,55,66]
>>> list3 =[77,88,99]
>>>map(abc,list1,list2,list3)
[114477,225588,336699]

>>> list1 =[11,22,33]
>>>map(None,list1)
[11,22,33]
>>> list1 =[11,22,33]
>>> list2 =[44,55,66]
>>> list3 =[77,88,99]
>>>map(None,list1,list2,list3)
[(11,44,77), (22,55,88), (33,66,99)]

[abc(a,b,c) for a in list1 for b in list2 for c in list3]

其实上面那个推导式应该这样写:
[abc(a,b,c) for a,b,c in zip(list1,list2,list3)]




堆 结构:

纸上谈兵: 堆 (heap)


堆(heap)又被为优先队列(priority queue)

在堆中,我们不是按照元素进入队列的先后顺序取出元素的,而是按照元素的优先级取出元素

这就好像候机的时候,无论谁先到达候机厅,总是头等舱的乘客先登机,然后是商务舱的乘客,最后是经济舱的乘客。

每个乘客都有头等舱、商务舱、经济舱三种个键值(key)中的一个


Linux内核中的调度器(scheduler)会按照各个进程的优先级来安排CPU执行哪一个进程。

计算机中通常有多个进程,每个进程有不同的优先级(该优先级的计算会综合多个因素,

比如进程所需要耗费的时间,进程已经等待的时间,用户的优先级,用户设定的进程优先程度等等)。

内核会找到优先级最高的进程,并执行。

如果有优先级更高的进程被提交,那么调度器会转而安排该进程运行。优先级比较低的进程则会等待。“堆”是实现调度器的理想数据结构。

(Linux中可以使用nice命令来影响进程的优先级)


堆的实现

堆的一个经典的实现是完全二叉树(complete binary tree)。这样实现的堆成为二叉堆(binary heap)。

完全二叉树是增加了限定条件的二叉树。假设一个二叉树的深度为n。为了满足完全二叉树的要求,该二叉树的前n-1层必须填满,第n层也必须按照从左到右的顺序被填满

为了实现堆的操作,我们额外增加一个要求: 任意节点的优先级不小于它的子节点。如果在上图中,设定小的元素值享有高的优先级,那么上图就符合该要求。


堆的主要操作

插入和删除最小元素(元素值本身为优先级键值,小元素享有高优先级)

操作之后,我们必须保持该实现应有的性质: 1. 完全二叉树 2. 每个节点值都小于或等于它的子节点。


插入操作的时候,会破坏上述堆的性质,所以需要进行名为percolate_up的操作,以进行恢复。

新插入的节点new放在完全二叉树最后的位置,再和父节点比较。

如果new节点比父节点小,那么交换两者。交换之后,继续和新的父节点比较…… 

直到new节点不比父节点小,或者new节点成为根节点。

这样得到的树,就恢复了堆的性质。




删除操作只能删除根节点。

根节点删除后,我们会有两个子树,我们需要基于它们重构堆。进行percolate_down的操作: 

让最后一个节点last成为新的节点,从而构成一个新的二叉树。

再将last节点不断的和子节点比较。如果last节点比两个子节点中小的那一个大,则和该子节点交换。

直到last节点不大于任一子节点都小,或者last节点成为叶节点。






heapq.merge:

Python使用heapq实现小顶堆(TopK大)、大顶堆(BtmK小)   

Python heapq模块


这个模块(build-in)实现了一个堆的数据结构,完美的解决了Top-K问题,以后解决Top-K问题的时候,直接把这个模块拿来用就可以了

注意,默认的heap是一个小顶堆!

 

heapq模块提供了如下几个函数:


heapq.heappush(heap, item) 把item添加到heap中(heap是一个列表)

heapq.heappop(heap) 把堆顶元素弹出,返回的就是堆顶

heapq.heappushpop(heap, item) 先把item加入到堆中,然后再pop,比heappush()再heappop()要快得多

heapq.heapreplace(heap, item) 先pop,然后再把item加入到堆中,比heappop()再heappush()要快得多

heapq.heapify(x) 将列表x进行堆调整,默认的是小顶堆

heapq.merge(*iterables) 将多个列表合并,并进行堆调整,返回的是合并后的列表的迭代器

heapq.nlargest(n, iterable, key=None) 返回最大的n个元素(Top-K问题)

heapq.nsmallest(n, iterable, key=None) 返回最小的n个元素(Top-K问题)


python中的堆排序peapq模块

>>> a=[2,4,6]         

>>> b=[1,3,5]

>>> c=merge(a,b)

>>> list(c)

[1,2, 3,4, 5,6]




next:

next方法

>>> t = g(5)

>>> t.next()

0

>>> t.next()

1

>>> t.next()

4

>>> t.next()

9

>>> t.next()

16

>>> t.next()

Traceback (most recent call last):

  File "<stdin>", line1, in<module>

StopIteration


ugly = next(merged)

没有找到想要的说明的文章,只能自己先写下笨笨的理解了:


当 primes 是 [2,7,13,19]时

uglies 是 [1]时

ugly = next(merged) 这条命令运行过程:gen 函数内 的 ugly 为 1,与 primes 里的每一个作用后,有个 yield 值,用 heapq.merge 排序后,next 取到 2


a。已经被计算过的 prime 和 ugly 就不会再一次被 yield,对于每个 prime ,会从上一次的 ugly 之后开始 yield 

b。先由 p*u:7*2 生成的14,后由  p*u:2*7 生成的14,会先把 后生成的 14 放在 uglies


A:每生成 一个 yield 的值,就会把这之前 接下来最小的数 放在 uglies 里面

B:优先级:uglies 里最新放入的值 是由哪对 p*u 生成的,则下一个 yield 由 相应的 p 继续生成

A,B 反复进行下去






你可能感兴趣的:(LeetCode,python)