1. 程式人生 > 其它 >LC39-組合總和----LC40-組合總和 II

LC39-組合總和----LC40-組合總和 II

技術標籤:演算法刷題剪枝資料結構演算法leetcode

文章目錄

39. 組合總和

難度中等1141

給定一個無重複元素的陣列 candidates 和一個目標數 target ,找出 candidates 中所有可以使數字和為 target 的組合。

candidates 中的數字可以無限制重複被選取。

說明:

  • 所有數字(包括 target)都是正整數。
  • 解集不能包含重複的組合。

示例 1:

輸入:candidates = [2,3,6,7], target = 7,
所求解集為:
[
  [7],
  [2,2,3]
]

思路分析:根據示例 1:輸入: candidates = [2, 3, 6, 7],target = 7。

候選數組裡有 2,如果找到了組合總和為 7 - 2 = 5 的所有組合,再在之前加上 2 ,就是 7 的所有組合;
同理考慮 3,如果找到了組合總和為 7 - 3 = 4 的所有組合,再在之前加上 3 ,就是 7 的所有組合,依次這樣找下去。

詳細思路,圖解詳情

回溯,減支

package com.nie.OneJanLC;
/* * *@auth wenzhao *@date 2021/1/26 11:03 */ import java.util.ArrayList; import java.util.List; public class LEE039 { public List<List<Integer>> combinationSum(int[] candidates, int target) { List<List<Integer>> res = new ArrayList<>(); getRes(res,
new ArrayList<>(), candidates, target, 0); return res; } /** * @param res 結果集合 * @param list 從根結點到葉子結點的路徑,是一個棧 * @param candidates 候選陣列 * @param target 每減去一個元素,目標值變小 * @param start 搜尋起點 */ private void getRes(List<List<Integer>> res, List<Integer> list, int[] candidates, int target, int start) { if (target == 0) { res.add(new ArrayList<>(list)); return; } // 重點理解這裡從 start 開始搜尋的語意 for (int i = start; i < candidates.length; i++) { // 重點理解這裡剪枝,前提是候選陣列已經有序, 陣列中剩餘的數大於要要求和的 if (target < candidates[i]) { continue; } list.add(candidates[i]); // 注意:由於每一個元素可以重複使用,下一輪搜尋的起點依然是 i,這裡非常容易弄錯 getRes(res, list, candidates, target - candidates[i], i); // 狀態重置 list.remove(list.size() - 1); } } }

40. 組合總和 II

難度中等478

給定一個數組 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]
]
package com.nie.OneJanLC;/*
 *
 *@auth  wenzhao
 *@date 2021/1/26  11:03
 */

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class LEE040 {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> res = new ArrayList<>();
        Arrays.sort(candidates);
        getRes(candidates, new ArrayList<>(), res, target, 0);
        return res;
    }

    private void getRes(int[] candidates, ArrayList<Integer> list, List<List<Integer>> res, int target, int start) {
        if (target == 0) {
            res.add(new ArrayList<>(list));
            return;
        }
        for (int i = start; i < candidates.length; i++) {
            // 大剪枝:減去 candidates[i] 小於 0,減去後面的 candidates[i + 1]、candidates[i + 2] 肯定也小於 0,因此用 break
            if (target < candidates[i]) {
                continue;
            }
            // 小剪枝:同一層相同數值的結點,從第 2 個開始,候選數更少,結果一定發生重複,因此跳過,用 continue
            if (i > start && candidates[i] == candidates[i - 1]) {
                continue;
            }
            list.add(candidates[i]);
            // 因為元素不可以重複使用,這裡遞迴傳遞下去的是 i + 1 而不是 i
            getRes(candidates, list, res, target - candidates[i], i + 1);
            list.remove(list.size() - 1);
        }
    }
}

:解釋語句: if cur > begin and candidates[cur-1] == candidates[cur] 是如何避免重複的。

摘選自

這個避免重複當思想是在是太重要了。
這個方法最重要的作用是,可以讓同一層級,不出現相同的元素。即
                  1
                 / \
                2   2  這種情況不會發生 但是卻允許了不同層級之間的重複即:
               /     \
              5       52
                  1
                 /
                2      這種情況確是允許的
               /
              2  
                
為何會有這種神奇的效果呢?
首先 cur-1 == cur 是用於判定當前元素是否和之前元素相同的語句。這個語句就能砍掉例1。
可是問題來了,如果把所有當前與之前一個元素相同的都砍掉,那麼例二的情況也會消失。 
因為當第二個2出現的時候,他就和前一個2相同了。
                
那麼如何保留例2呢?
那麼就用cur > begin 來避免這種情況,你發現例1中的兩個2是處在同一個層級上的,
例2的兩個2是處在不同層級上的。
在一個for迴圈中,所有被遍歷到的數都是屬於一個層級的。我們要讓一個層級中,
必須出現且只出現一個2,那麼就放過第一個出現重複的2,但不放過後面出現的2。
第一個出現的2的特點就是 cur == begin. 第二個出現的2 特點是cur > begin.