1. 程式人生 > 其它 >力扣-40-組合總和Ⅱ

力扣-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. 和排列的區別:
    這裡的一次排序肯定是有額外的時間複雜度的,主要是這一趟排序能巧妙地解決三個問題,如果是像排序那樣(1不能重複選,2接收排列不同看作是不同的結果,3沒有重複數字),不使用一趟排序而是用交換可能效率更高

  2. 和原題的區別:1原題無限制取本題只能用一次,2原題不包含重複元素本題包含
    相同點是對於不同的排列看作是相同的結果