LeetCode第39题思悟——组合总和(combination-sum)

LeetCode第39题思悟——组合总和(combination-sum)

知识点预告

  1. 数组的排序处理;
  2. 分治思想的应用;
  3. 递归结果的返回处理;

题目要求

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

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

说明:

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

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

示例

示例 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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

我的思路

涉及数组中查找,必不可少的操作是排序,接着就是二分查找;算是一种组合拳吧;根据题目描述,这道题可以采用分治的处理思路:在一组数中,寻找一些数,使它们之和等于target,那么当选定一个数时,target就会变小一点,而问题的本质却没有发生变化,这符合分治的处理手段;简单来说,就是不断选定一个数,然后将target变小,然后递归处理;

public List<List<Integer>> combinationSum(int[] candidates, int target) {
	if(candidates.length==0){
		return new ArrayList<>();
	}
	Arrays.sort(candidates);
	return combinationSum(candidates,target,0);
}
private boolean contains(int[] candidates,int start,int end,int target){
	int left=start,right=end;
	int middle;
	while(left<=right){
		middle=(left+right)/2;
		if(candidates[middle]<target){
			left=middle+1;
		}else if(candidates[middle]>target){
			right=middle-1;
		}else{
			return true;
		}
	}
	return false;
}
private List<List<Integer>> combinationSum(int[] candidates,int target,int start){
	List<List<Integer>> result=new ArrayList<>();
	List<List<Integer>> subResult;
	int index=start;
	int headValue=candidates[index];
	int realTarget=target-candidates[index];
	List<Integer> item;
	if(contains(candidates,start,candidates.length-1,target)){
		item=new ArrayList<>();
		item.add(target);
		result.add(item);
	}
	while(headValue<=realTarget){
		subResult=combinationSum(candidates,realTarget,index);
		if(subResult.size()!=0){
			for (List<Integer> answer : subResult) {
				answer.add(headValue);
				result.add(answer);
			}
		}
		index++;
		if (index <candidates.length) {
			headValue = candidates[index];
			realTarget=target-headValue;
		} else {
			break;
		}
	}
	return result;
}

优秀解法

//解法A
public List<List<Integer>> combinationSum(int[] candidates, int target) {
	List<List<Integer>> listAll = new ArrayList<List<Integer>>();
	List<Integer> list = new ArrayList<Integer>();
	Arrays.sort(candidates);
	find(listAll,list,candidates,target,0);
	return listAll;
}
public void find(List<List<Integer>> listAll,List<Integer> tmp,int []
		candidates,int target,int num){
	if(target == 0){
		listAll.add(tmp);
		return;
	}
	if(target <candidates[0])
		return ;
	for(int i = num;i<candidates.length&&candidates[i]<=target;i++){
		List<Integer> list = new ArrayList<>(tmp);
		list.add(candidates[i]);
		find(listAll,list,candidates,target-candidates[i],i);
	}
}
//解法B
public List<List<Integer>> combinationSum(int[] candidates, int target) {
	List<List<Integer>> res = new ArrayList<>();//模板
	Arrays.sort(candidates);//数之和就肯定要排序 不然指针没法操作
	//System.out.println(candidates);
	backtrack(candidates, target, res, 0, new ArrayList<Integer>());
	return res;
}
private void backtrack(int[] candidates, int target, List<List<Integer>> res, int i, ArrayList<Integer> tmp_list) {
	if (target < 0) return;//没啥意义
	if (target == 0) {//target逐渐减小 直到0的时刻返回
		res.add(new ArrayList<>(tmp_list));//加入答案并返回 和模板没有区别
		return;
	}
	for (int start = i; start < candidates.length; start++) {
		if (target < candidates[start]) break;
		//System.out.println(start);
		tmp_list.add(candidates[start]);
		//System.out.println(tmp_list);
		backtrack(candidates, target - candidates[start], res, start, tmp_list);
		tmp_list.remove(tmp_list.size() - 1);
		//回溯与剪枝的关系????
	}
}	

差异分析

解法A和解法B在解题思路上是一致的,都是将数组的首位放到结果里,然后修改target,起到分治的效果;而我的解法里,思路一致,只是会判断一下是否存在剩余的那个数,如果存在,就算是发现了一个答案,只需要添加即可;

总结到这里,我有一点怀疑,是否二分查找有一点多余呢?毕竟优秀解法里提供的解法均没有使用到二分查找;实际上,是可以去掉的,只是去掉后,对于递归方法调用的退出条件需要再做修改,并且对结果的返回也要再做处理;而如此依赖,实际上就和优秀解法里提供的方法没什么区别了;

知识点小结

  1. 数组的排序处理;
  2. 分治思想的应用;
  3. 递归结果的返回处理;
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值