LC39-組合總和----LC40-組合總和 II
阿新 • • 發佈:2021-01-31
文章目錄
- [39. 組合總和](https://leetcode-cn.com/problems/combination-sum/)
- [40. 組合總和 II](https://leetcode-cn.com/problems/combination-sum-ii/)
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 5
例2
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.