给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
一道回溯经典应用题。
题目要求的是组合 不是排列,也就是 [1,2] [2,1] 是一个答案,别弄错了。
回溯 、递归模板
这种回溯的题,都可以画成树形结构,不熟的时候,先画图看看逻辑,
1.确定 递归/回溯 参数:
首先需要题目给出的n和k没跑了,另外还有一个,可以在图中看到,当选择1之后,只能从2,3,4里再选,选择2之后,只能从剩下的3,4里选,也就是,选择i之后,只能从i+1里去选,所以要有一个记录下标的值startindex。
def backtrack(n,k,startindex):
2.确定终止条件:
这个比较好想,当用来记录的数组里已经有k个值了,那么就终止。
在终止之前,要将收集到的数加入到答案集。
if len(temp) == k:
res.append(temp[:])
return
3.确定循环体:
循环体里也就是从一层到下面一层 and 下面一层回溯到上面一层 的过程中需要操作的东西。
首先需要将当前的 元素放到记录的数组中,然后调用自己,最后回溯的时候记着弹出元素。
temp.append(i)
backtrack(n,k,i+1)
temp.pop()
细节:
比如:
temp.append(i) # 这就是从本层到下一层需要做的事
backtrack(n,k,i+1) # 这就是回调函数
temp.pop() # 这就是从下一层到上一层需要做的事
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
temp = []
def backtrack(n,k,startindex):
# 终止条件
if len(temp) == k:
res.append(temp[:])
#假设说如果你直接加入temp的话,那么temp一定是你一开始要设置得全局变量得一个数组list对吧,然后你每次都往res中存入得temp其实就是一个指针,当你递归完以后,回溯,将path里的最后一个数据删除了,那么res中存入得元素指针,指向得那个数组同样需要删除那个元素,最后就会导致,你在res中开辟了多个空间,但是最后每个数组指代得是同一块空间,并且最后该空间内得所有元素,最后都是空。
return
# 循环体
for i in range(startindex,n+1): # 记着+1,题目n从1开始的
temp.append(i)
backtrack(n,k,i+1)
temp.pop()
backtrack(n,k,1)
return res
回溯其实就是纯暴力算法,只是有时候不能无脑嵌套for,倒不是时间复杂度的问题,而是有时候根本没法写,比如这个题的for,有几个k就有几层嵌套for,但是k不确定,所以没法写。
当使用回溯的时候,往往搭配着剪枝,以降低时间复杂度。
假设n=4,k=4 也就是 一共四个数,取4个数,这里盗用一下卡哥的图,可以看到 除了最左边的一条,其余都不符合要求,都可以剪掉。
那么如何在代码中控制需要剪掉的分支呢?
假设目前n=4 ,k=3。看一下需要剪掉的部分,当记录数组temp中为空,且当前i取3的时候,剩下可取元素为4,那么取了3再进入一下分支取4,temp中也仅有两个值。显然这个i=3的分支是需要剪掉的,也就是 当你temp数组中个数+还需要的元素个数>剩下可选的元素个数时,剪! 换句话说,你的 i 最多只能遍历到2,遍历到2,temp里是2,然后还能取3和4,此时正好为3个元素。
也就是说找一个公式来控制 i 最多可以遍历到的值,使得剩下未遍历的元素+temp里现有的元素可以满足k的要求
剩下未遍历的元素就是 元素总和n - 当前遍历到的下标i, -> n-i
即:(多项式优化)
所以for里的 i 条件是 i <= n-(k-len(temp), 也就是最多遍历到这,而之前是直接遍历到n+1。
实际上代码里是n-(k-len(temp))+1+1 ,为啥要+1两次呢,随便带个k和n就知道了,其实是题目n从1开始的原因。另一个+1就是无剪枝的情况下带的。
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
# 带剪枝
res = []
temp =[]
def backtrack(n,k,startindex):
# 终止条件
if len(temp) == k:
res.append(temp[:])
return
for i in range(startindex,n-(k-len(temp))+1+1):
temp.append(i)
backtrack(n,k,i+1)
temp.pop()
backtrack(n,k,1)
return res