77. 组合
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
1 | 输入:n = 4, k = 2 |
示例 2:
1 | 输入:n = 1, k = 1 |
提示:
1 <= n <= 20
1 <= k <= n
思路:
回溯算法
回溯法解决的问题都可以抽象为树形结构来理解,如下图
其中n(集合长度)相当于树的宽度,k(组合子集的长度)相当于树的深度。
每次搜索到叶子结点,就找到了一个子集结果,然后只要把所有的从根节点搜索到叶子结点的路径收集起来,就可以求出结果集。
回溯三部曲:
- 递归函数的返回值以及参数
- 返回值,一般递归函数的返回值都为空
- 参数:
- n:集合中元素个数,遍历宽度(for循环宽度)
- k:组合子集中元素个数,遍历深度(递归深度)
- startIndex:用于记录下一层递归搜索的起始位置
- 回溯终止条件
- 从根节点到达叶子节点即结束本层递归
- 也就是说path数组的大小如果达到k,说明我们找到了一个子集大小为k的组合了,在图中path存的就是根节点到叶子节点的路径
- 单层搜索逻辑
- for循环每次从startIndex开始遍历,然后用path保存取到的节点i
- 然后通过递归函数不断调用自己 一直往深处遍历,直到遇到了叶子节点再返回。
- 回溯操作,比如这次的遍历结果是{1,2},这个结果已经保存在结果集res中了,但是我们还要遍历{1,3},{1,4},所以需要进行回溯,把2剔除,然后3,4才能进去。
回溯算法模板:
1 | void backtracking(参数) { |
代码:
1 | //单条路径 |
剪枝:
来举一个例子,n = 4,k = 4的话,那么第一层for循环的时候,从元素2开始的遍历都没有意义了,因为从元素2到元素4一共才三个元素,构不成一个4位的集合。 在第二层for循环,从元素3开始的遍历都没有意义了。
如图:
也就是说:
如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
优化过程:
- 已经选择了的元素个数:path.size();
- 还需要选择的元素个数:k - path.size();
- 从集合n和还需要的元素个数(k - path.size)反推startIndex,可得在集合n中至多要从该起始位置 : n - (k - path.size()) + 1的地方开始遍历。
代码优化:
1 | //单条路径 |
参考自代码随想录:https://programmercarl.com/0077.%E7%BB%84%E5%90%88.html#%E5%89%AA%E6%9E%9D%E4%BC%98%E5%8C%96