題目描述(題目難度,中等)
給定一個無重複元素的數組 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
著作權歸領釦網絡所有。商業轉載請聯繫官方授權,非商業轉載請註明出處。
題目求解
一時沒有想到什麼巧妙的解法,考慮用遞歸求解。
憑感覺快速寫了一個遞歸求解的版本:
代碼如下:
class Solution {
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
comSum(candidates, target, result, new ArrayList<>());
return result;
}
void comSum(int[] can, int tar, List<List<Integer>> res, ArrayList<Integer> canRes){
if(tar == 0){
res.add(new ArrayList<>(canRes));
return;
}
if(tar < 0) return;
for(int i = 0; i < can.length; ++i){
if(can[i] <= tar){
canRes.add(can[i]);
comSum(can, tar-can[i], res, canRes);
canRes.remove(canRes.size()-1);
}
}
}
}
執行測試用例後結果如下:
可以看到,這樣遞歸求解,解集中包含了重複的組合。所以自然而然的想到,對求解後的結果集進行去重處理。直觀的去重處理的辦法是,將列表排序,然後對長度相等的列表逐元素比較,判斷列表是否相等,如果等,則最終只保留一個。但是我感覺把所有列表排序效率太低,考慮有沒有更有效的去重手段。
考慮的方案一:
對等長列表所有元素做異或運算,以異或的結果判斷列表是否相等。相等的列表異或結果一定相等,但不等的列表異或結果就一定不等嗎?經過研究後,發現不一定!例如列表 {12(1100), 3(0011)} 和列表 {10(1010), 5(0101)},是兩個和都爲 15 的不同列表,但異或結果相同,也都是 15。
考慮的方案二:
對等長列表所有元素做累乘運算,以乘積結果判斷列表是否相等。理論上感覺可行,編程實現後,發現還是不行,提交代碼得到了一個反例 {3, 8, 10},{4, 5, 12},和都爲 21,積都爲 240。
思來想去,想不出什麼去重的好辦法。最後感覺這個題不應該這樣解,這樣解既複雜又不直觀。考慮改進遞歸,觀察上一版本代碼的輸出,想到在遞歸選數的過程中,能不能不選擇已選數字之前的數字。例如對於輸入 {2, 3, 6, 7},在選擇 3 之後,就不再選擇 2,那麼就不會出現像 {2, 3, 2},{3, 2, 2} 這樣的重複組合了。
按上述思路改進後的遞歸代碼如下:
public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> result = new ArrayList<>();
comSum(candidates, target, result, new ArrayList<>(), 0);
return result;
}
void comSum(int[] can, int tar, List<List<Integer>> res, ArrayList<Integer> canRes, int low){
if(tar == 0){
res.add(new ArrayList<>(canRes));
return;
}
if(tar < 0) return;
for(int i = low; i < can.length; ++i){
if(can[i] <= tar){
canRes.add(can[i]);
comSum(can, tar-can[i], res, canRes, i);
canRes.remove(canRes.size()-1);
}
}
}
給遞歸函數增加了一個參數,控制選數的起始位置。代碼提交後,運行通過。
另外,有意思的是,如果把遞歸調用的代碼 comSum(can, tar-can[i], res, canRes, i)
的最後一個實參稍作修改,改爲 comSum(can, tar-can[i], res, canRes, i+1)
。這樣修改的意思是,遞歸後,從當前元素的後面開始選數,當前元素只會被選擇一次,那麼求解的就是不重複選數和等於給定值的問題。