当某个数的素因子只包括a,b,c时,意味着这个数可以拆分成 ax1 * bx2 * cx3 也就是若干个a若干个b若干个c相乘后再相乘,比如下面这一题:
M17.09 第k个数
这一类题目普遍有两种做法,一是堆,二是动态规划
归根结底,第k个数一定是前面的k-1个数中的某个数 *a 或 *b 或 *c 得到的,具体是哪一个数乘以几得到的其实是要一个个算的,最暴力的方法就是每一次算第k个数的时候就对前面k-1个数分别×素因数,取出第一个大于第 k-1 个数的数,他就是第k个数,一个个的算直到找到答案,代码如下:
暴力:
# 这种解法非常耗时
def getKthMagicNumber(self, k: int) -> int:
al_nums = [1]
while k-1:
# 使用set去重
t = set()
for i in al_nums:
t.add(i * 3)
t.add(i * 5)
t.add(i * 7)
t = list(t)
t.sort()
index = bisect_left(t, al_nums[-1])
if t[index] == al_nums[-1]:
index += 1
al_nums.append(t[index])
k -= 1
return al_nums[-1]
方法一、堆
上面的暴力解法中,每一次都是从头算,从而浪费了大量的时间,其实可以反着算,算开头的那个数,比如,开始时是1,将1乘以3,5,7得到 [1,3,5,7]那么第一个数就是1,后面的数不可能由1×3,5,7得到了,所以将1踢出去(其实是将1记作第一个数,我们要找的是第k个数),将3乘以3,5,7得到9,15,21,加入堆得到 [3,5,7,9,15,21],后面的数不可能再由3乘3,5,7得到了,所以将3踢出去(这就是最终答案的第2个数,我们要求第k个数),然后对5进行同样的操作并踢出去,直到踢到第k个数就是答案,由于这个数组需要始终保证最小的数在开头并且会被不断地踢出去,所以要用到堆!
def getKthMagicNumber(k: int):
q, vis = [1], set()
while k:
# 这个t就是前面最小的从来没有乘过的数
t = heapq.heappop(q)
k -= 1
if k == 0:
return t
vis.add(t)
for i in [3, 5, 7]:
if t * i not in vis:
heapq.heappush(q, t * i)
vis.add(t * i)
return -1
方法二:动态规划
其实,上面的解析也能看出来,第k个数是通过前面的k-1个数生成的,换句话说第k个数与前面的数有着非常紧密的关系,这不就是动态规划吗!第k个数会由前面k-1个数中的某一个数乘3或乘5或乘7得到,那么关键就是哪一个数乘以几的问题,动态规划解决的就是这个问题
由于可能是某一个数乘以3,5,7得到,所以记录三个位置,p3,p5,p7分别表示要乘3,5,7的值的位置,刚开始时这三个位置都在1处,然后每一次循环就将这三个(初始在同一个地方)乘以对应的值,判断哪一个值是最小的,将这个最小的记下来,并将得到这个最小值的指针加一其他的指针不动,一直循环下去,这样每一个值都会乘三个质因数各一次,且不断地涨,所以正确
def getKthMagicNumber(k: int):
ans = [0] * k
ans[0] = 1
q3, q5, q7 = 0, 0, 0
for i in range(1, k):
t = min(ans[q3] * 3, ans[q5] * 5, ans[q7] * 7)
ans[i] = t
if t == ans[q3] * 3:
q3 += 1
if t == ans[q5] * 5:
q5 += 1
if t == ans[q7] * 7:
q7 += 1
return ans[-1]