原题:题目链接
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
二话不说,直接开始怼代码 (^ ▽ ^)
利用回溯的思想,如示例中的情况。我们先选出1,然后从[2,3,4]中分别挑出一个数与之结合,直到temp数组长度满足要求,则退出递归。之后再考虑选出2,从剩下的[3,4]中分别挑出一个数与之结合…一直进行,当函数进行到选出4之后,我们显然无法从一个空数组[]中选出任何一个数,因此退出。代码如下:
def combine(n, k):
if n < k:
# 如果被选集合小于要选出的个数 直接返回
return
# 结果集
res = []
# 待扩充的集合
temp = []
# 回溯函数模板
def backtrace(start, n, k, temp, res):
if len(temp) == k:
# 加入结果集 注意深拷贝
res.append(temp[:])
return
for i in range(start, n+1):
temp.append(i)
backtrace(i+1, n, k, temp, res)
# 回溯弹出
temp.pop()
backtrace(1, n, k, temp, res)
return res
上述代码能通,只是比较低效,似乎有可以优化的空间。为此需要仔细分析该回溯方法的一种可行的剪枝方法,先看看这行代码:
for i in range(start, n+1)
我们其实是没必要每次都遍历到n的。举个栗子,当n=5,k=3的时候,很明显,如果确定了temp的第一个数为1的话,当temp=[1,4,5]并加入结果集的时候就应该直接退出,考虑第一个数为2的情形了。但上面的代码却做了以下操作:
5
,此时temp=[1,4]
4
,此时temp=[1]
5
,此时temp=[1,5]
5
,此时temp=[1]
很容易看出这几个操作是多余的。因此我们需要找一个i
的上界,让循环提前退出即可。我们需要保证,在剩余可加入数集中的数的个数需>=
temp数组中还需要添加的数的个数。设该当前考虑的i
值为limit,则有:
因此,limit的上界为n+1-(k-len(temp))
,因此就有如下的优化代码:
def combine(n, k):
if n < k:
# 如果被选集合小于要选出的个数 直接返回
return
# 结果集
res = []
# 待扩充的集合
temp = []
# 回溯函数模板
def backtrace(start, n, k, temp, res):
if len(temp) == k:
# 加入结果集 注意深拷贝
res.append(temp[:])
return
for i in range(start, n+2-(k-len(temp))):
temp.append(i)
backtrace(i+1, n, k, temp, res)
# 回溯弹出
temp.pop()
backtrace(1, n, k, temp, res)
return res
从提交结果可以看出,算法效率的确提高不少。
参考的代码:链接
整体上就是上面那位大神写的代码,只是我在刚开始看的时候花了一些些时间(毕竟菜鸟┭┮﹏┭┮)。在此仅将其做一些注释,来日方长,还需学习。
def combine(self, n: int, k: int) -> List[List[int]]:
if n < k:
return
res = list()
nums = [x for x in range(1, n+1)]
ind = [i for i in range(k)]
res.append([nums[i] for i in ind])
while True:
for i in reversed(range(k)):
if ind[i] < n + i - k:
# n + i - k为当前ind[i]的上界
# 如果彼此相等 则考虑更新ind的下一个位置
break
else:
# ind里面的值都达到了对应的上界 则直接返回结果
return res
# 考虑加入下一个值
ind[i] += 1
# 更新ind中i后面的所有值
# 类似于开启回溯程序中的新的for循环 可以达到去重的效果
for j in range(i+1, k):
ind[j] = ind[j-1] + 1
#加入解集
res.append([nums[i] for i in ind])
本质上的思想同回溯算法一样。只是该迭代版本通过不断地改变加入解集的索引值来达到扩充temp数组的目的。具体的注释加入到代码中,如果有不对的或者不清楚,欢迎提出来。这样彼此都能学习提高。
第一篇博客任务达成!
请原谅以往无知的自己吧,你毕竟拥有了现在,干就完事了!!!