LeetCode第39題思悟——組合總和(combination-sum)
知識點預告
- 數組的排序處理;
- 分治思想的應用;
- 遞歸結果的返回處理;
題目要求
給定一個無重複元素的數組 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,起到分治的效果;而我的解法裏,思路一致,只是會判斷一下是否存在剩餘的那個數,如果存在,就算是發現了一個答案,只需要添加即可;
總結到這裏,我有一點懷疑,是否二分查找有一點多餘呢?畢竟優秀解法裏提供的解法均沒有使用到二分查找;實際上,是可以去掉的,只是去掉後,對於遞歸方法調用的退出條件需要再做修改,並且對結果的返回也要再做處理;而如此依賴,實際上就和優秀解法裏提供的方法沒什麼區別了;
知識點小結
- 數組的排序處理;
- 分治思想的應用;
- 遞歸結果的返回處理;