赞
踩
题意理解:
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。如:n=3,k=2,则有:12 13 23
一般,我们使用回溯法来解决组合问题。
组合问题没有顺序要求,所以 12 21 是同一个组合(如果是排列12 21 是两种排列)
一般我们可以把回溯法要解决的问题抽象成树结构:
集合大小=树的宽度=n 组合大小=递归深度=k
所以:
我们可以将这个问题抽象为树问题,在递归方法中使用回溯来暴力搜索。
由于回溯是暴力解锁的方式,为了实现性能优化,我们提前对于一些不可能搜到正确结果的树枝进行剪枝操作。
注意:
removeLast()是LinkedList的方法,List<> list=new LinkedListM<>()是调用不到的。
添加结果是,path的内容要复制给一个新的对象new LinkedList<>(),否则对同一个path对象修改的话,results记录的结果值也会发生改变,最终的结果就是result里面重复加入相同的path对象。。
- /**
- * 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合
- * @param n
- * @param k
- * @return
- */
- List<List<Integer>> results=new ArrayList<>();
- LinkedList<Integer> path = new LinkedList<>();
- public List<List<Integer>> combine(int n, int k) {
- backtracking(n,k,1);
- return results;
- }
- public void backtracking(int n,int k,int startIndex){
- //结果收集,结束
- if(path.size()==k){
- //重新new一个是因为如果对同一个path操作,最终result是一堆相同的path
- results.add(new ArrayList<>(path));
- return;
- }
- //未收集结果,遍历当前分支子孩子
- for(int i=startIndex;i<=n;i++){
- path.add(i);
- backtracking(n,k,i+1);
- path.removeLast();
- }
- }
采用剪枝是为了在进行暴力的回溯搜索时,及时剪除不可能搜到正确结果的树枝,对整体搜索过程进行优化。
图摘自《代码随想录》
【剪枝优化】 从startIndex收集path n-pathSize=还需要收集的数据量 n-statIndex=剩余的数据量 如果 k-pathSize<n-startIndex+1,则剩余的搜索,现有数据不足以搜到大小为k的组合 所以我们要保证k-pathSize<n-startIndex+1,变形得 startIndex<n-(k-pathSize)+1
- /**
- * 给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合
- * @param n
- * @param k
- * @return
- */
- List<List<Integer>> results=new ArrayList<>();
- LinkedList<Integer> path = new LinkedList<>();
- public List<List<Integer>> combine(int n, int k) {
- backtracking(n,k,1);
- return results;
- }
- public void backtracking(int n,int k,int startIndex){
- //结果收集,结束
- if(path.size()==k){
- //重新new一个是因为如果对同一个path操作,最终result是一堆相同的path
- results.add(new ArrayList<>(path));
- return;
- }
- //未收集结果,遍历当前分支子孩子
- //【剪枝优化】
- for(int i=startIndex;i<=n-(k-path.size())+1;i++){
- path.add(i);
- backtracking(n,k,i+1);
- path.removeLast();
- }
- }
时间复杂度:
暴力回溯:O(2^n)
剪枝回溯:O()
空间复杂度:
暴力回溯:O(k)
简直回溯:O(k)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。