39.组合总和

39.组合总和

题目

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

所有数字(包括 target)都是正整数。
解集不能包含重复的组合。

示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

题解

思路

这道题的本质还是枚举,找出所有符合条件的情况,那么可以使用回溯算法。

candidates中的数字可以无限制重复被选取,说明每次可取的元素都是整个数组。

抽象树
image

从抽象树可以看出以下几个问题:
1.递归的次数是不确定的,那么就需要考虑什么时候停止递归?
2.给定的candidates数组是否有序的?如果有序那么当前元素+之前线路总和>target,之后的元素也不用取值了。
3.解集不能包含重复的组合,如何避免重复?

问题的解
1.给定的candidates数组是无序的,那么递归的终止条件是sum>=target
2.避免重复就得区分排列和组合
在排列中,[2,2,3]和[2,3,2]不是重复的
在组合中,[2,2,3]和[2,3,2]是重复的
解决重复的办法是使用startIndex控制每次循环的取值

startindex的使用

开始看见可以重复取值就想的不用startIndex了,startIndex是解决重复的关键。
对于组合问题

  • 多个集合取组合,各个集合之间互不影响,就不用startIndex
  • 一个集合取组合,就需要startIndex

抽象树中就使用了startIndex,为什么取3之后下一次就不取2或者5了?
对于组合来说[2,2,3]与[3,2,2]是重复的,3与2组合结果已经在2与3里讨论过了,避免重复就不需要取2了。

题解

在上述的引入下,套上递归三部曲

递归函数的返回值以及参数
candidates:从candidates数组取值
target:组合的总数
startIndex:控制candidates数组的取值区间
sum:路径总和

List<List<Integer>> res= new ArrayList<List<Integer>>(); //结果集
List<Integer> path = new ArrayList();//记录当前的路径
void backtracking(int[] candidates,int target,int startIndex,int sum);

递归的终止条件

if(sum>target) return
if(sum==target){
	res.add(new ArrayList(path));
	return;
}

单层递归逻辑

for(int i=startIndex;i<candidates.length;i++){
//当前元素加入路径
int cur = candidates[i];
path.add(cur);
backtracking(candidates,target,i,sum+cur);//可以取重复的元素,控制下一层递归开始取值的下标
path.remove(path.size()-1);
}

代码

class Solution {
    List<List<Integer>> res;
    List<Integer> path;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        res = new ArrayList<>();
        path = new ArrayList<>();
        backtracking(candidates,target,0,0);
        return res;
    }
    void backtracking(int[] candidates,int target,int startIndex,int sum){
        if(sum > target) return;
        if(sum == target){
	        res.add(new ArrayList<>(path));
	        return;
        }
       for(int i=startIndex;i<candidates.length;i++){
         path.add(candidates[i]);
         backtracking(candidates,target,i,sum+candidates[i]);
         path.remove(path.size()-1);
    }
    }
}

剪枝

在本层递归循环的时候,可以先预判sum+candidates[i]本次循环取值>target?这样可以少做加入path与从path中删除的步骤,以此实现优化。

 for(int i=startIndex;i<candidates.length;i++){
    if(sum+candidates[i] > target) continue;//修改点
    path.add(candidates[i]);
    backtracking(candidates,target,i,sum+candidates[i]);
    path.remove(path.size()-1);
    }

学习-利用排序

做40题时,不会去重,看别人的题解可以利用排序法,所以打算重做此题。

我的题解思路其实提出过考虑给定的candidates数组是否有序的?如果有序candidates[i]+sum>target,i之后元素也不用取值了,从而实现剪枝提高效率。因为给定的candidates时无序的,我就没有继续考虑这种情况了。
按这个思路,可以得出排序的主要目的时剪枝

class Solution {
    List<List<Integer>> res=new ArrayList<List<Integer>>();
    List<Integer> path ;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        res = new ArrayList<>();
        path=new ArrayList<Integer>();
         Arrays.sort(candidates);//对candidates进行排序,排序时剪枝的前提
           backtracking(candidates,target,0,0);
        return res;
    }
    void backtracking(int[] candidates,int target,int startIndex,int sum){
        if(sum == target){
	        res.add(new ArrayList<>(path));
	        return;
        }
       for(int i=startIndex;i<candidates.length;i++){
         if(sum+candidates[i]>target) break; //与之前剪枝的不同点
         path.add(candidates[i]);
         backtracking(candidates,target,i,sum+candidates[i]);
         path.remove(path.size()-1);
    }
    }
}
posted @ 2021-05-29 11:07  rananie  阅读(58)  评论(0编辑  收藏  举报