力扣-40-組合總和Ⅱ
複習下原題,之前做過的,4個月前了
第一眼看到覺得是完全揹包,但是好像不太一樣
然後想到了回溯
我很快寫了一個標準的回溯出來,但是意識到好像不太對
class Solution { public: vector<vector<int>> res; vector<int> temp; vector<vector<int>> combinationSum(vector<int>& candidates, int target) { backTrack(candidates, target); return res; } void backTrack(vector<int>& nums,int target) { if (target < 0) return; if (target == 0) { res.push_back(temp); return; } for (int i : nums) { temp.push_back(i); backTrack(nums,target-i); temp.pop_back(); } } };
它跟排列和組合都不一樣,它可以重複選,但是最終的結果會被認為是重複的同一結果,直觀來說就是這樣:
2 2 3
2 3 2
3 2 2
7
前三個我們認為是同一個,我現在要做的就是去重
稍微還有點映像,是每次只有比上一個數字大才插進去
即如果每次選擇的元素大於上一個元素,這樣可以保證得到唯一的遞增組合
想到的最簡單的實現就是加一個遞迴引數判斷
class Solution { public: vector<vector<int>> res; vector<int> temp; vector<vector<int>> combinationSum(vector<int>& candidates, int target) { backTrack(candidates, target,0); return res; } void backTrack(vector<int>& nums,int target,int pre) { if (target < 0) return; if (target == 0) { res.push_back(temp); return; } for (int i : nums) { if (i < pre) continue; temp.push_back(i); backTrack(nums,target-i,i); temp.pop_back(); } } };
回溯+單增去重,可能是最直接的實現了
正文
然後才是看今天的題目,基本上沒差,就只是不能重複使用了
有點像是去重後的排列的感覺
之前排列標記已選過的元素用的是交換的方式,通過一個標記index來標記當前排到了多少位並於當前選中元素的位置交換
class Solution { public: vector<vector<int>> res; vector<int> temp; vector<vector<int>> combinationSum(vector<int>& candidates, int target) { backTrack(candidates, target,0,0); return res; } void backTrack(vector<int>& nums,int target,int index,int pre) { if (target < 0) return; if (target == 0) { res.push_back(temp); return; } for (int i = index; i < nums.size(); i++) { if (nums[i] < pre) continue; temp.push_back(nums[i]); swap(nums[index], nums[i]); // 注意這裡要減去的目標位置被交換了,所以不是num[i]而是nums[index] backTrack(nums, target - nums[index], index + 1,nums[index]); swap(nums[index], nums[i]); temp.pop_back(); } } };
我照著前面的思路寫出來,但是結果是這樣的
1 2 5
1 7
1 1 6
2 6
1 1 6
1 2 5
1 7
我意識到不是我之前的做法有問題,而是這裡題目不一樣了,題目陣列中是有相同元素的
輸入: candidates = [10,1,2,7,6,1,5], target = 8,
嗯,題目不允許重複選擇,結果也不允許重複,但是又有相同元素
應該說是同一輪選擇中不能選中重複的相同元素,但是怎麼做到呢?
關鍵是在for (int i = index; i < nums.size(); i++) {
這裡跳過重複的元素
啊,我突然意識到了評論1的巧妙
排序一次的話,就完全就不需要我做的兩步操作(同組遞增,和交換位置防止重選,這樣也沒法解決上面的問題)
如果是排序後的陣列中選擇的話,就只需要像組合一樣,選後面的就完事了,1來絕對不會選重,2來保證了所有的結果遞增(排列唯一),3來還能方便對每一層for去重
class Solution {
public:
vector<vector<int>> res;
vector<int> temp;
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(), candidates.end());
backTrack(candidates, target, 0);
return res;
}
void backTrack(vector<int>& nums, int target, int index) {
if (target < 0) return;
if (target == 0) {
res.push_back(temp);
return;
}
for (int i = index; i < nums.size(); i++) {
if (i > index && nums[i] == nums[i - 1]) continue;
temp.push_back(nums[i]);
backTrack(nums, target - nums[i], i + 1);
temp.pop_back();
}
}
};
所以總結一下,
-
和排列的區別:
這裡的一次排序肯定是有額外的時間複雜度的,主要是這一趟排序能巧妙地解決三個問題,如果是像排序那樣(1不能重複選,2接收排列不同看作是不同的結果,3沒有重複數字),不使用一趟排序而是用交換可能效率更高 -
和原題的區別:1原題無限制取本題只能用一次,2原題不包含重複元素本題包含
相同點是對於不同的排列看作是相同的結果