零. 子集、组合和排列问题汇总
一. 子集问题
1.1 子集扩张
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { if(nums.empty()) return vector<vector<int>>(1, vector<int>()); int last = nums.back(); nums.pop_back(); vector<vector<int>> sub = subsets(nums); int sz = sub.size(); for(int i=0; i<sz; ++i){ vector<int> tmp = sub[i]; tmp.push_back(last); sub.push_back(tmp); } return sub; } };
|
1.2 回溯1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void traceback(vector<int>& nums, int pos){ if(pos == nums.size()){ ret.push_back(cur); return; } cur.push_back(nums[pos]); traceback(nums, pos+1); cur.pop_back(); traceback(nums, pos+1); } vector<vector<int>> subsets(vector<int>& nums) { traceback(nums, 0); return ret; } };
|
1.3 回溯2(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void traceback(vector<int>& nums, int pos){ ret.push_back(cur); for(int i=pos; i<nums.size(); ++i){ cur.push_back(nums[i]); traceback(nums, i+1); cur.pop_back(); } } vector<vector<int>> subsets(vector<int>& nums) { traceback(nums, 0); return ret; } };
|
- 两条值相同的相邻树枝会产生重复,如果一个节点有多条值相同的树枝相邻,则只遍历第一条,剩下的都剪掉,不要去遍历(不能让队员越权)
- “使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过,一个维度是同一树层上使用过。 我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重。(注意:这里的同一树层指的是由同一个父节点引出的子节点,叔伯的子节点可以用相同下一个值,比如下图[1]接2和[2]接2,都可以用2;但是[1]接2就不能接2'了!)
2.1 回溯1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: vector<int> cur; vector<vector<int>> ret;
void traceback(bool choosePre, int pos, vector<int> &nums) { if (pos == nums.size()) { ret.push_back(t); return; } traceback(false, cur + 1, nums); if (!choosePre && pos > 0 && nums[pos - 1] == nums[pos]) return; cur.push_back(nums[pos]); traceback(true, pos + 1, nums); cur.pop_back(); }
vector<vector<int>> subsetsWithDup(vector<int> &nums) { sort(nums.begin(), nums.end()); traceback(false, 0, nums); return ret; } };
|
2.2 回溯2(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, int pos){ ret.push_back(cur); for(int i=pos; i<nums.size(); ++i){ if(i > pos && nums[i] == nums[i-1]) continue; cur.push_back(nums[i]); backtrack(nums, i+1); cur.pop_back(); } } vector<vector<int>> subsetsWithDup(vector<int>& nums) { sort(nums.begin(), nums.end()); backtrack(nums, 0); return ret; } };
|
2.3 回溯2(易读版利用used判断树层重复)
也可用used数组来简化理解,nums[i]
与nums[i-1]
相等,且nums[i-1]
没有使用的情况下,表明重复了,因为只有在nums[i-1]
用过之后才会不用,而nums[i-1]
之前用过,其后的子树是已经加入到结果了,即可跳过当前点 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { private: vector<vector<int>> ret; vector<int> cur; void backtracking(vector<int>& nums, int pos, vector<bool>& used) { ret.push_back(cur); for (int i = pos; i < nums.size(); i++) { if (i > 0 && nums[i] == nums[i - 1] && !used[i-1]) continue; used[i] = true; cur.push_back(nums[i]); backtracking(nums, i + 1, used); cur.pop_back(); used[i] = false; } }
public: vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<bool> used(nums.size(), false); sort(nums.begin(), nums.end()); backtracking(nums, 0, used); return ret; } };
|
二. 组合问题
1.1 回溯
nums中的每个数字可以多次使用 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, int pos, int target){ if(target <= 0){ if(target == 0) ret.push_back(cur); return; } for(int i=pos; i<nums.size(); ++i){ cur.push_back(nums[i]); backtrack(nums, i, target-nums[i]); cur.pop_back(); } } vector<vector<int>> combinationSum(vector<int>& nums, int target) { backtrack(nums, 0, target); return ret; } };
|
nums中的每个数字在每个组合中只能使用一次 && 解集不能包含重复的组合。
说这是一个组合问题,其实换个问法就变成子集问题了:请你计算 candidates 中所有和为 target 的子集。
2.1 回溯1
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, int pos, int target, bool preUsed){ if(target < 0) return; if(target == 0){ ret.push_back(cur); return; } if(pos >= nums.size()) return; if(nums[pos] > target) return; backtrack(nums, pos+1, target, false); if(pos>0 && nums[pos-1] == nums[pos] && !preUsed) return; cur.push_back(nums[pos]); backtrack(nums, pos+1, target - nums[pos], true); cur.pop_back(); } vector<vector<int>> combinationSum2(vector<int>& nums, int target) { sort(nums.begin(), nums.end()); backtrack(nums, 0, target, false); return ret; } };
|
2.2 回溯2(★)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, int pos, int target){ if(target == 0){ ret.push_back(cur); return; } for(int i=pos; i<nums.size(); ++i){ if(i>pos && nums[i-1] == nums[i]) continue; if(target < nums[i]) return; cur.push_back(nums[i]); backtrack(nums, i+1, target-nums[i]); cur.pop_back(); } } vector<vector<int>> combinationSum2(vector<int>& nums, int target) { sort(nums.begin(), nums.end()); backtrack(nums, 0, target); return ret; } };
|
三. 排列问题
不含重复数字的数组:nums,经典回溯 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, vector<bool>& used){ if(cur.size() == nums.size()){ ret.push_back(cur); return; } for(int i=0; i<nums.size(); ++i){ if(used[i]) continue; used[i] = true; cur.push_back(nums[i]); backtrack(nums, used); cur.pop_back(); used[i] = false; } } vector<vector<int>> permute(vector<int>& nums) { vector<bool> used(nums.size(), false); backtrack(nums, used); return ret; } };
|
含有重复数字的数组:nums
2.1 哈希(有意思)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: vector<vector<int>> res; void traceback(map<int, int>& hash, vector<int>& cur, int n){ if(cur.size() == n){ res.push_back(cur); return; } for(map<int, int>::iterator itr=hash.begin(); itr!=hash.end(); ++itr){ if(itr->second){ --itr->second; cur.push_back(itr->first); traceback(hash, cur, n); cur.pop_back(); ++itr->second; } } } vector<vector<int>> permuteUnique(vector<int>& nums) { map<int, int> hash; vector<int> cur; for(auto e: nums) ++hash[e]; traceback(hash, cur, nums.size()); return res; } };
|
2.2 回溯(跟子集问题2.3很像)
当出现重复元素时,比如输入 nums = [1,2,2',2''],2' 只有在 2 已经被使用的情况下才会被选择,2'' 只有在 2' 已经被使用的情况下才会被选择,这就保证了相同元素在排列中的相对位置保证固定。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| class Solution { public: vector<vector<int>> ret; vector<int> cur; void backtrack(vector<int>& nums, vector<bool>& used){ if(cur.size() == nums.size()){ ret.push_back(cur); return; } for(int i=0; i<nums.size(); ++i){ if(i>0 && nums[i]==nums[i-1] && !used[i-1]) continue; if(used[i]) continue; used[i] = true; cur.push_back(nums[i]); backtrack(nums, used); cur.pop_back(); used[i] = false; } } vector<vector<int>> permuteUnique(vector<int>& nums) { sort(nums.begin(), nums.end()); vector<bool> used(nums.size(), false); backtrack(nums, used); return ret; } };
|