【LeetCode】39. 組合總和

39. 組合總和

知識點:遞歸;回溯;組合;剪枝java

題目描述

給定一個無重複元素的正整數數組 candidates 和一個正整數 target ,找出 candidates 中全部可使數字和爲目標數 target 的惟一組合。python

candidates 中的數字能夠無限制重複被選取。若是至少一個所選數字數量不一樣,則兩種組合是惟一的。算法

對於給定的輸入,保證和爲 target 的惟一組合數少於 150 個。數組

示例
輸入: candidates = [2,3,6,7], target = 7
輸出: [[7],[2,2,3]]

輸入: candidates = [2,3,5], target = 8
輸出: [[2,2,2,2],[2,3,3],[3,5]]   

輸入: candidates = [2], target = 1
輸出: []  

輸入: candidates = [1], target = 1
輸出: [[1]]  

輸入: candidates = [1], target = 2
輸出: [[1,1]]

解法一:回溯

回溯算法的模板:函數

result = []   //結果集
def backtrack(路徑, 選擇列表):
    if 知足結束條件:
        result.add(路徑)  //把已經作出的選擇添加到結果集;
        return  //通常的回溯函數返回值都是空;

    for 選擇 in 選擇列表: //其實每一個題的不一樣很大程度上體如今選擇列表上,要注意這個列表的更新,
    //好比多是搜索起點和重點,好比多是已經達到某個條件,好比可能已經選過了不能再選;
        作選擇  //把新的選擇添加到路徑裏;路徑.add(選擇)
        backtrack(路徑, 選擇列表) //遞歸;
        撤銷選擇  //回溯的過程;路徑.remove(選擇)

核心就是for循環裏的遞歸,在遞歸以前作選擇,在遞歸以後撤銷選擇;優化


對於本題,有兩點和77題組合不同:ui

  • 此題能夠重複選取選過的元素,因此選擇列表的搜索起點不用i+1,仍然是i。
  • 此題沒有像以前的題明確給出遞歸的層數,可是給了target,因此若是相加>target,那就證實到頭了;

咱們換個角度從新畫這個圖,和77題有點差距,理解的更全面一點。 其實這就是一個橫向循環和縱向的遞歸,橫向循環作出不一樣的選擇,縱向在不一樣的選擇基礎上作下一步選擇。code

image

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Stack<Integer> path = new Stack<>();
        backtrack(candidates, target, 0, 0, res, path);
        return res;
    }
    private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){
        if(sum > target){
            return; 
        }
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = begin; i < candidates.length; i++){
            //作選擇;
            sum += candidates[i];
            path.push(candidates[i]);
            //遞歸:開始下一輪選擇;
            backtrack(candidates, target, sum, i, res, path);  //不用+1,能夠重複選;
            //撤銷選擇:回溯
            sum -= candidates[i];
            path.pop();
        }
    }
}

解法二:剪枝優化

上述程序有優化的空間,咱們能夠對數組先進行排序,而後若是找到了當前的sum已經等於target或大於target了,那後面的就能夠直接跳過了,由於後面的元素更大,確定更大於target。排序

image

class Solution {
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Stack<Integer> path = new Stack<>();
        Arrays.sort(candidates);  //排序
        backtrack(candidates, target, 0, 0, res, path);
        return res;
    }
    private void backtrack(int[] candidates, int target, int sum, int begin, List<List<Integer>> res, Stack<Integer> path){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = begin; i < candidates.length && sum + candidates[i] <= target; i++){ 
            //剪枝:若是sum+candidates[i] > target就結束;
            //作選擇;
            sum += candidates[i];
            path.push(candidates[i]);
            //遞歸:開始下一輪選擇;
            backtrack(candidates, target, sum, i, res, path);  //不用+1,能夠重複選;
            //撤銷選擇:回溯
            sum -= candidates[i];
            path.pop();
        }
    }
}

體會

  • 要可以把這種決策樹畫出來;
  • 在求和問題中,排序以後加上剪枝是很常見的操做,可以捨棄無關的操做;

相關連接

回溯算法入門級介紹!
組合問題遞歸

相關文章
相關標籤/搜索