回溯+剪枝

目錄

 

回溯+剪枝

概念

例題

練習


回溯+剪枝

概念

回溯算法也叫試探法,它是一種系統地搜索問題的解的方法。

用回溯算法解決問題的一般步驟:

1、 針對所給問題,定義問題的解空間,它至少包含問題的一個(最優)解。

2 、確定易於搜索的解空間結構,使得能用回溯法方便地搜索整個解空間 。

3 、以深度優先的方式搜索解空間,並且在搜索過程中用剪枝函數避免無效搜索。

確定瞭解空間的組織結構後,回溯法就從開始結點(根結點)出發,以深度優先的方式搜索整個解空間。這個開始結點就成爲一個活結點,同時也成爲當前的擴展結點。在當前的擴展結點處,搜索向縱深方向移至一個新結點。這個新結點就成爲一個新的活結點,併成爲當前擴展結點。如果在當前的擴展結點處不能再向縱深方向移動,則當前擴展結點就成爲死結點。此時,應往回移動(回溯)至最近的一個活結點處,並使這個活結點成爲當前的擴展結點。回溯法即以這種工作方式遞歸地在解空間中搜索,直至找到所要求的解或解空間中已沒有活結點時爲止。

參考文獻:https://baike.baidu.com/item/%E5%9B%9E%E6%BA%AF%E7%AE%97%E6%B3%95/9258495?fr=aladdin

例題

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
       著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。

一般來說,解決回溯問題,畫圖更有助於理解,我們在這裏也可以畫一個圖。因爲要使用深度優先搜索,所以圖應該是一個樹的結構。以上題爲例:

圖1

首先我們可以對candidates做一個排序,方便我們加枝。

這裏我以0作爲根節點,來做加法,當節點走到7或者大於7的時候終止。如果節點值爲7,認爲找到一條結果,將其加入結果集,如果大於7,則剪去。

因爲對於candidates做了排序,所以一旦出現上述情況,就可以進行回溯。

對着圖1,我們可以得到結果集爲:

[
    [2,2,3]
    [2,3,2]
    [3,2,2]
    [7]
]

然而預期結果爲

[
    [7]
    [2,2,3]
]

顯然,現在得到的結果是有問題的,結果集中存在重複的結果。我們可以這樣考慮,在越靠近左邊的枝中,會得到一個內容從小到大的結果,其後重複的結果都不是順序排列的,所以我們可以剪去那些下邊枝條值小於上邊枝條值的分支。

圖2

 

即可得到正確結果。

代碼實現:

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);
            }
        }
    }
}

練習

40. 組合總和 II

給定一個數組 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);
            }
        }
    }
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章