给定一个集合S, S到集合T的映射是S中的元素任意组合之和构成的集合。现在可以往S中加一些数,使得集合T能覆盖1至N,求加数的最少的个数。
分析:
1. 先通过一个例子来体会其中的规律
假设S = {2, 3, 5},首先1必须加进去,1的加入使得集合变成了S1 = {1, 2, 3, 5},此时的T = {1,2,3,4,5,6,7,8,9,10,11},即T的区间为[1, 11],此时还不能达到目标。若想达到[1, 20],再往其中加任意一个大于位于区间[9, 12]的数都可以达到目标【因为小于9无法达到20,大于12又会出现11后面的断层】。其实可以发现,假设S映射到T集合的区间为[1, m],若此时我们再加入一个数a,则能够拥有新区间[a, a+m]。为了在不出现断层的情况下使得新合并区间最大,我们应该贪心的加入a=m+1,此时新区间为[1, 2*m+1],其实一旦a<m+1,就会有一段区间重合,这是非常低效的,所以我们避开重合,而尽量去扩展区间。这种贪心思想就是这道题目的精髓。
2. 更多细节
由于原始数组中已经存在一些数,所以在并非每一次区间更新都是由于我们主动加入新数。区间更新还有可能由数组中已经存在的数导致。举个例子,S = {1, 2, 4, 6},在我们从左往右遍历的过程中,{1, 2, 4}能够产生T∈[1,7],而位于该区间内的 6 的加入,会更新T∈[1, 13]。这样看来,我们只需要维护一个区间的右界right,一旦有界内的数在nums里面,我们就可以去更新右界right了。直到遍历到right+1,说明当前集合S只能使得T最大为right了,若想扩展,则必须往S中加入新数,根据之前的分析,我们贪心的加入num=right+1,同时更新区间右界为2*right+1。
nums = [1, 3, 10]
target = 23
right = 0
cnt = 0
i = 0
add = []
while right < target:
while right < target and i <= right:
i += 1
if i in nums:
right += i
if right >= target:
break
cnt += 1
right += i
add.append(i)
print(cnt)
print(add)
特殊情况:
当nums为空时,我们会发现每次加入的数为1,2,4,8,……,2**k,所以k=log2 N 向上取整。也可以用二分法去找到一个临界的k值