[Leetcode]组合
题目描述
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
解题思路
- 如果解决一个问题有多个步骤,每个步骤有多种方法,题目又要我们找出所有的方法,可以使用回溯算法
- 回溯算法是在一棵树上的 深度优先遍历
- 组合问题相对于排列问题而言,不计较一个组合内元素的顺序性,即 [1, 2, 3] 与 [1, 3, 2]
认为是同一个组合,因此很多时候需要按某种顺序展开搜索才能做到不重不漏 - 根据搜索起点画出二叉树
- 例如输入:n=4, k=2
- 每个节点结点递归的在做同一件事情
实现代码
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
if n < k or k <= 0:
return res
def dfs(begin, path):
if len(path) == k: #递归结束条件:path的长度等于k
res.append(path[:])
return
for i in range(begin, n + 1): #遍历可能的搜索起点
path.append(i)
dfs(i + 1, path) #下一轮搜索
del path[-1] #重点理解这里:深度优先遍历有回头的过程,因此递归之前做了什么,递归之后需要做相同操作的逆向操作
dfs(1,[])
return res
优化:剪枝
- 如果 n=7,k=4,从5开始搜索就已经没有意义了,因为即使把5选上,后面的数只有6和7,一共就3个候选数,凑不出4个数的组合,因此,搜索起点有上界
- 例如,n=6,k=4
path.size() == 1 的时候,接下来要选择 3 个数,搜索起点最大是 4,最后一个被选的组合是 [4, 5, 6];
path.size() == 2 的时候,接下来要选择 2 个数,搜索起点最大是 5,最后一个被选的组合是 [5, 6];
path.size() == 3 的时候,接下来要选择 1 个数,搜索起点最大是 6,最后一个被选的组合是 [6];
- 再如,n = 15 ,k = 4。
path.size() == 1 的时候,接下来要选择 3 个数,搜索起点最大是 13,最后一个被选的是 [13, 14, 15];
path.size() == 2 的时候,接下来要选择 2 个数,搜索起点最大是 14,最后一个被选的是 [14, 15];
path.size() == 3 的时候,接下来要选择 1 个数,搜索起点最大是 15,最后一个被选的是 [15];
- 可以归纳出: 搜 索 起 点 的 上 界 + 接 下 来 要 选 择 的 元 素 个 数 − 1 = n 搜索起点的上界 + 接下来要选择的元素个数 - 1 = n 搜索起点的上界+接下来要选择的元素个数−1=n
- 其中, 接 下 来 要 选 择 的 元 素 个 数 = k − l e n ( p a t h ) 接下来要选择的元素个数 = k - len(path) 接下来要选择的元素个数=k−len(path)
- 整理得到: 搜 索 起 点 的 上 界 = n − ( k − l e n ( p a t h ) ) + 1 搜索起点的上界 = n- (k - len(path))+ 1 搜索起点的上界=n−(k−len(path))+1
- 所以剪枝的过程就是把 i < = n i<= n i<=n 改成 i < = n − ( k − l e n ( p a t h ) ) + 1 i<=n-(k-len(path))+1 i<=n−(k−len(path))+1
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
res = []
if n < k or k <= 0:
return res
def dfs(begin, path):
if len(path) == k:
res.append(path[:])
return
for i in range(begin, n - (k - len(path)) + 2): #剪枝过程
path.append(i)
dfs(i + 1, path)
del path[-1]
dfs(1,[])
return res