目錄
回溯+剪枝
概念
回溯算法也叫試探法,它是一種系統地搜索問題的解的方法。
用回溯算法解決問題的一般步驟:
1、 針對所給問題,定義問題的解空間,它至少包含問題的一個(最優)解。
2 、確定易於搜索的解空間結構,使得能用回溯法方便地搜索整個解空間 。
3 、以深度優先的方式搜索解空間,並且在搜索過程中用剪枝函數避免無效搜索。
確定瞭解空間的組織結構後,回溯法就從開始結點(根結點)出發,以深度優先的方式搜索整個解空間。這個開始結點就成爲一個活結點,同時也成爲當前的擴展結點。在當前的擴展結點處,搜索向縱深方向移至一個新結點。這個新結點就成爲一個新的活結點,併成爲當前擴展結點。如果在當前的擴展結點處不能再向縱深方向移動,則當前擴展結點就成爲死結點。此時,應往回移動(回溯)至最近的一個活結點處,並使這個活結點成爲當前的擴展結點。回溯法即以這種工作方式遞歸地在解空間中搜索,直至找到所要求的解或解空間中已沒有活結點時爲止。
參考文獻:https://baike.baidu.com/item/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95/9258495?fr=aladdin
例題
給定一個無重複元素的數組 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做一個排序,方便我們加枝。
這裏我以0作爲根節點,來做加法,當節點走到7或者大於7的時候終止。如果節點值爲7,認爲找到一條結果,將其加入結果集,如果大於7,則剪去。
因爲對於candidates做了排序,所以一旦出現上述情況,就可以進行回溯。
對着圖1,我們可以得到結果集爲:
[
[2,2,3]
[2,3,2]
[3,2,2]
[7]
]
然而預期結果爲
[
[7]
[2,2,3]
]
顯然,現在得到的結果是有問題的,結果集中存在重複的結果。我們可以這樣考慮,在越靠近左邊的枝中,會得到一個內容從小到大的結果,其後重複的結果都不是順序排列的,所以我們可以剪去那些下邊枝條值小於上邊枝條值的分支。
即可得到正確結果。
代碼實現:
import java.util.*;
class Solution {
List<List<Integer>> res=new ArrayList();
List<Integer> temp=new ArrayList();
int target=0;
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates.length<=0) return res;
this.target=target;
Arrays.sort(candidates);
List<Integer> temp=new ArrayList();
deep(candidates,0,0,temp);
return res;
}
//index的作用爲剪去那些下邊枝條值小於上邊枝條值的分支
public void deep(int[] candidates,int value,int index, List<Integer> temp){
if(value >target) return ;
if(value==target) {
res.add(new ArrayList(temp));
}
if(value<target){
for(int i=index;i<candidates.length;i++){
temp.add(candidates[i]);
deep(candidates,value+candidates[i],i,temp);
temp.remove(temp.size()-1);
}
}
}
}
練習
給定一個數組 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和爲 target 的組合。
candidates 中的每個數字在每個組合中只能使用一次。
說明:
所有數字(包括目標數)都是正整數。解集不能包含重複的組合。
示例 1:
輸入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集爲:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
輸入: candidates = [2,5,2,1,2], target = 5,
所求解集爲:
[
[1,2,2],
[5]
]
來源:力扣(LeetCode)
鏈接:https://leetcode-cn.com/problems/combination-sum-ii
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
這道題在39題的基礎上做的話會顯得非常簡單,我們只要在遞歸的時候設置下一次能夠使用的candidates的索引值爲i+1就行,這樣就能使得每一個數在一個組合裏只使用一次。除此之外在將temp加入res之前,我們需要判斷一下是否重複,因爲candidates中可能會存在重複的數字。
import java.util.*;
class Solution {
List<List<Integer>> res=new ArrayList();
List<Integer> temp=new ArrayList();
int target=0;
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if(candidates.length<=0) return res;
this.target=target;
Arrays.sort(candidates);
List<Integer> temp=new ArrayList();
deep(candidates,0,0,temp);
return res;
}
//index的作用爲剪去那些下邊枝條值小於上邊枝條值的分支
public void deep(int[] candidates,int value,int index, List<Integer> temp){
if(value >target) return ;
if(value==target) {
if(!res.contains(temp)) res.add(new ArrayList(temp));
}
if(value<target){
for(int i=index;i<candidates.length;i++){
temp.add(candidates[i]);
deep(candidates,value+candidates[i],i+1,temp);
temp.remove(temp.size()-1);
}
}
}
}