【LeetCode】40. 組合總和 II

40. 組合總和 II

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

題目描述

給定一個數組 candidates 和一個目標數 target ,找出 candidates 中全部可使數字和爲 target 的組合。數組

candidates 中的每一個數字在每一個組合中只能使用一次。函數

注意:解集不能包含重複的組合。ui

示例
輸入: candidates = [10,1,2,7,6,1,5], target = 8,
輸出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

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

解法一:回溯

回溯算法的模板:code

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

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

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


本題又和39題不同了,咱們來看一下不同的點在哪裏遞歸

  • 本題數組中的每一個數字在每一個組合裏只能選一次,不能重複選擇。 -- > 因此每次的選擇列表裏的搜索起點從i+1開始。
  • 本題中的數組是有重複的,39題是無重複的。這帶來一個問題就是可能會選到同樣的,舉個例子,就好比上面的例2,排完序後是12225,而後第一種組合選了122(注意這個樹枝上咱們是能夠選第2個2的,由於自己它就有不少嘛),而後第2個2撤銷了,選第3個2,這實際上是不能夠的,由於這也是122,重複了。因此能夠總結:一條樹枝能夠重複,同一樹層不能夠重複

因此使用一個used數組來標記那個元素被選擇過。candidates[i] == candidates[i - 1] 而且 used[i - 1] == false,就說明:前一個樹枝,使用了candidates[i - 1],也就是說同一樹層使用過candidates[i - 1]。leetcode

image

能夠看出在candidates[i] == candidates[i - 1]相同的狀況下:rem

  • used[i - 1] == true,說明同一樹支candidates[i - 1]使用過
  • used[i - 1] == false,說明同一樹層candidates[i - 1]使用過
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Stack<Integer> path = new Stack<>();
        boolean[] used = new boolean[candidates.length];  //標記誰被選擇過;
        Arrays.sort(candidates);
        backtrack(candidates, target, 0, 0, res, path, used);
        return res;
    }
    private void backtrack(int[] candidates, int target, int begin, int sum, List<List<Integer>> res, Stack<Integer> path, boolean[] used){
        if(sum == target){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = begin; i < candidates.length && sum + candidates[i] <= target; i++){
            if(i > 0 && candidates[i] == candidates[i-1] && !used[i-1]) {
                continue;  //前一樹枝用過了;
            } 
            //作選擇
            sum += candidates[i];
            path.push(candidates[i]);
            used[i] = true;
            //遞歸:開始下一輪選擇;
            backtrack(candidates, target, i+1, sum, res, path, used);
            //撤銷選擇:回溯;
            sum -= candidates[i];
            path.pop();
            used[i] = false;
        }
    }
}

體會

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

相關連接

組合總和2get

相關文章
相關標籤/搜索