leetcode 39. 組合總和(Java版)

題目描述(題目難度,中等)

給定一個無重複元素的數組 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)。這樣修改的意思是,遞歸後,從當前元素的後面開始選數,當前元素只會被選擇一次,那麼求解的就是不重複選數和等於給定值的問題。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章