78. 子集
給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
輸入: nums = [1,2,3]
輸出: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
子集: 對每個元素都有 取與不取 兩個狀態,所以共有2^n
種子集,正好可以使用對應位的二進制掩碼來判斷每個元素的狀態,1取0不取,共有1<<n
種狀態
拿題目來舉例子:
對應二進制數字爲:000 001 010 011 100 101 110 111
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int len = nums.size();
if(len<1) return vector<vector<int>> {{}};
int bit_map = 0;
int end = 1<<len; // 2^n class
vector<vector<int>> res;
vector<int> tmp;
// 2^n個掩碼(狀態),然後取當前掩碼對應的子集,需要遍歷數組 n*2^n
while(bit_map<end){
for(int i=0;i<len;i++)
if((1<<i)&bit_map) tmp.push_back(nums[i]);
res.push_back(tmp);
tmp.clear();
bit_map++;
}
return res;
}
};
複雜度分析
時間複雜度:O(N×2^N),生成所有的子集,並複製到輸出列表中。
空間複雜度:O(2^N),存儲所有子集,共N個元素,每個元素都有可能存在或者不存在。
遞歸回溯 求解
其實存在兩種遞歸求解的方法,一種是把解空間當做二叉樹進行深度優先或者廣度優先的遍歷,一種則是把解空間當做圖(多叉樹)進行深度優先的遍歷。
- 當成一個滿二叉樹 求解, 對於每一個 元素 都有 選與不選 兩個結果,對應左右節點,這樣每個葉子節點都是一個結果,但是這樣處理會 遍歷所有節點,速度會稍慢。
class Solution {
public:
vector<vector<int>> res;
void dfs(const vector<int>& nums,int index,vector<int>& ans){
if(index == nums.size()) res.push_back(ans);
if(index < nums.size()){
dfs(nums,index+1,ans); // 不選
ans.push_back(nums[index]);
dfs(nums,index+1,ans); // 選
ans.pop_back(); // 刪除 回溯到上一個狀態
}
}
vector<vector<int>> subsets(vector<int>& nums) {
if(nums.size()<1) return vector<vector<int>> {{}};
vector<int> ans;
dfs(nums,0,ans);
return res;
}
};
// [[],[3],[2],[2,3],[1],[1,3],[1,2],[1,2,3]]
深度搜索,一直不選擇到底,然後回溯慢慢添加元素,
- 回溯
它的每一個結點是滿足題目要求的一個解,結點之間的相鄰關係取決於該結點是否還可以塞入其他元素。
class Solution {
public:
vector<vector<int>> res;
void backtrack(const vector<int> nums,int index,vector<int>& tmp){
res.push_back(tmp);
for(int i=index;i<nums.size();i++){
tmp.push_back(nums[i]);
backtrack(nums,i+1,tmp);
tmp.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
if(!nums.size()) return res;
vector<int> tmp;
backtrack(nums,0,tmp);
return res;
}
};
遞歸回溯可能模板
void dfs(待搜索集合,遞歸層數,狀態變量1,2,結果集){
// 終止條件 return
// 或者 達到搜索結果 return
for(可執行分支){
// 剪枝
if 遞歸到第幾層, 狀態變量 1, 狀態變量 2, 符合一定的剪枝條件:
continue
// do something 對狀態變量的操作
dfs(待搜索集合,遞歸層數+1,狀態變量1,2,結果集); // 繼續遞歸
// do something 回溯 對狀態變量的操作
// 如 push_back(i) pop_back() 刪除這個元素,回溯到添加這個元素上一步
}
}
90 子集II
回溯
去重,首先對數組進行排序,使重複元素挨在一起
然後回溯時,如果同一層分支中存在重複元素,則跳過
class Solution {
public:
vector<vector<int>> res;
void backtrack(const vector<int> nums,int index,vector<int>& tmp){
res.push_back(tmp);
for(int i=index;i<nums.size();i++){
//同層遞歸中,第二次遇到重複元素跳出
if(i != index && nums[i-1]==nums[i])
continue;
tmp.push_back(nums[i]);
backtrack(nums,i+1,tmp);
tmp.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
if(!nums.size()) return res;
vector<int> tmp;
sort(nums.begin(),nums.end());
backtrack(nums,0,tmp);
return res;
}
};
77 組合
回溯
- 使用多叉圖的方法,對每個
class Solution {
public:
vector<vector<int>> res;
void backtrack(int n,int k,vector<int>& tmp,int index){
if(tmp.size() == k) res.push_back(tmp);
for(int i=index;i<=n;i++){
tmp.push_back(i);
backtrack(n,k,tmp,i+1);
tmp.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
if(k<1) return res;
vector<int> tmp;
backtrack(n,k,tmp,1);
return res;
}
};
二叉樹 深搜
- 對每個節點,都有選擇與不選擇 兩個狀態,分別對應兩個分支, 但是因爲取到k個元素就滿足,所以遞歸到k深度 即可返回
class Solution {
public:
vector<vector<int>> res;
void dfs(int n,int k,int index,vector<int>& tmp){
if(tmp.size() == k) {
res.push_back(tmp);
return ;
}
if(index<=n && tmp.size() < k){
dfs(n,k,index+1,tmp);
tmp.push_back(index);
dfs(n,k,index+1,tmp);
tmp.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
if(k<1) return vector<vector<int>> {{}};
vector<int> tmp;
dfs(n,k,1,tmp);
return res;
}
};
46 全排列
class Solution {
public:
vector<vector<int>> res;
void dfs(const vector<int> nums,vector<bool>& dp,vector<int>& tmp,int len){
if(tmp.size() == len) res.push_back(tmp);
for(int i=0;i<len;i++){
if(!dp[i]){
tmp.push_back(nums[i]);
dp[i] = true;
dfs(nums,dp,tmp,len);
tmp.pop_back();
dp[i] = false;
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
int len = nums.size();
if(!len) return res;
vector<bool> dp(len,false);
vector<int> tmp;
dfs(nums,dp,tmp,len);
return res;
}
};
47 全排列II
class Solution {
public:
vector<vector<int>> res;
void dfs(const vector<int> nums,vector<bool>& dp,vector<int>& tmp,int len){
if(tmp.size() == len) res.push_back(tmp);
for(int i=0;i<len;i++){
if(i!=0 && nums[i-1]==nums[i] && !dp[i-1]) continue;
if(!dp[i]){
tmp.push_back(nums[i]);
dp[i] = true;
dfs(nums,dp,tmp,len);
tmp.pop_back();
dp[i] = false;
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
int len = nums.size();
if(!len) return res;
sort(nums.begin(),nums.end());
vector<bool> dp(len,false);
vector<int> tmp;
dfs(nums,dp,tmp,len);
return res;
}
};
39 組合總和
- 回溯算法
這一題的難點在於數字可以無限制重複,但是又要避免解集中出現重複,由此想到累加的下一次要從自身的index開始。
遞歸條件 加上當前元素 不超過目標值 則可以進一步遞歸 超過則退出
可以先排序,然後超過元素之後的所有元素都可以跳過 不排序的話,就對所有元素進行遍歷,判斷是否進入下一層遞歸
去重
如果不去重,會產生 [223] [232] [322] 這些類似結果
class Solution {
public:
vector<vector<int>> res;
void dfs(const vector<int> nums,int sum,int target,int len,vector<int>& tmp,int index){
if(sum == target) {
res.push_back(tmp);
return ;
}
for(int i=index;i<len;i++){
if(sum+nums[i]>target) continue;
tmp.push_back(nums[i]);
dfs(nums,sum+nums[i],target,len,tmp,i);
tmp.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
int len = candidates.size();
if(!len) return res;
vector<int> tmp;
dfs(candidates,0,target,len,tmp,0);
return res;
}
};
// sort(nums)
// if(sum+nums[i]>target) break;
// 排序後 如果當前元素大於目標值 則後面所有元素都大於 直接跳出循環
40 組合總和II
回溯
- 每個字符只能使用一次 所以將index當做狀態變量遞歸
- 去重,首先對數組進行排序,重複的元素在一起,遞歸時 判斷上次元素和這次元素相等則跳出,
- 剪枝 如果當前元素已經大於目標值,則跳出循環,此後所有元素不滿足 目標狀態
class Solution {
public:
vector<vector<int>> res;
void dfs(const vector<int>& nums,int target,int sum,int index,vector<int>& tmp){
if(sum == target) res.push_back(tmp);
for(int i=index;i<nums.size() && sum+nums[i]<=target;i++){
if(i!=index && nums[i-1]==nums[i]) continue;
tmp.push_back(nums[i]);
dfs(nums,target,sum+nums[i],i+1,tmp);
tmp.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
int len = candidates.size();
if(!len) return res;
sort(candidates.begin(),candidates.end());
vector<int> tmp;
dfs(candidates,target,0,0,tmp);
return res;
}
};
關於去重一個比較清晰的解釋
其實就是如何 去除同層的重複 並且保留不同層的相同值
if(i!=index && nums[i-1]==nums[i]) continue;
主要實現就是i!=index
,重複元素在同層第二次取到時 一定滿足i!=index
所以將它去除