leetcode--遞迴、回溯和分治
基本概念
leetcode題目
78. Subsets
題意:
給出一個數列,求出這個數列所有子集
解題思路:
用遞迴解決,設計思路:
資料結構:
一個vector<int>
item儲存集合
一個vector<vector<int>>
result 儲存結果
遞迴程式:
generate(i,nums,item,result)
遞迴程式含義:nums[i]前所有元素+nums[i]後所有元素組成的所有子集放入result中
結束條件:i=nums.size()
遞迴過程:
item.push_back(nums[i]);
generate(i+1 ,nums,item,result);
item.pop(nums[i]);
generate(i+1,nums,item,result);
程式碼:
class Solution {
public:
std::vector<std::vector<int> > subsets(std::vector<int>& nums) {
std::vector<std::vector<int> > result;
std::vector<int> item;
result.push_back(item);//先將空集壓入
generate(0, nums, item, result);
return result;
}
private:
void generate(int i, std::vector<int>& nums,
std::vector<int> &item,
std::vector<std::vector<int> > &result){
if (i >= nums.size()){
return ;
}
item.push_back(nums[i]);
result.push_back(item);
generate(i + 1, nums, item, result);
item.pop_back();
generate(i + 1, nums, item, result);
}
};
思路2:位運演算法(迴圈解法)
位運算基本操作:
解題思路:
如果集合內有n個數,那麼就有2^n個子集
從0到2^(n-1)迴圈,用二進位制表示,每一個數表示一種集合。
每一個數根據每個位,遍歷n個數是否在所表示的集合內。
程式碼:
public:
vector<vector<int>> subsets(vector<int>& nums) {
vector<vector<int>> result;
int n=nums.size();
int all_set=1<<n;
for(int i=0;i<all_set;i++){
vector<int> item;
for(int j=0;j<n;j++){
if(i&(1<<j)) item.push_back(nums[j]);
}
result.push_back(item);
}
return result;
}
};
90. Subsets II
題意:
找子集,要求子集沒有重複。
解題思路:
有兩種重複的子集,一種是元素和順序都相同,一種只有元素相同
1:[1,2,2]和[1,2,2]
2:[1,2,2]和[2,1,2]
對於第一種重複,可以用set去除
對於第二種重複,可以先對元素進行排序,使它只剩下第一種重複
程式碼:
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());
vector<vector<int>> result;
set<vector<int>> res_set;
vector<int> item;
result.push_back(item);
generate(0,nums,item,res_set,result);
return result;
}
private:
void generate(int i,vector<int>& nums,vector<int> &item,set<vector<int>> &res_set,vector<vector<int>> &result){
if(i==nums.size()) return;
item.push_back(nums[i]);
if(res_set.find(item)==res_set.end()){
res_set.insert(item);
result.push_back(item);
}
generate(i+1,nums,item,res_set,result);
item.pop_back();
generate(i+1,nums,item,res_set,result);
}
};
40. Combination Sum II
題意:
找到相加之為和為T的子集。
解題思路
運用減枝的思想,縮減遞迴運算的時間複雜度,就是已經不滿足的條件的分枝不再進行遞迴。
程式碼:
class Solution {
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<int> item;
vector<vector<int>> result;
set<vector<int>> res_set;
sort(candidates.begin(),candidates.end());
generate(0,candidates,item,result,res_set,0,target);
return result;
}
private:
void generate(int i,vector<int> &candidates, vector<int> &item,vector<vector<int>> &result,set<vector<int>> &res_set,int sum,int target){
if(i>=candidates.size()||sum>target) return;
item.push_back(candidates[i]);
sum+=candidates[i];
if(res_set.find(item)==res_set.end()&&sum==target){
res_set.insert(item);
result.push_back(item);
}
generate(i+1,candidates,item,result,res_set,sum,target);
item.pop_back();
sum-=candidates[i];
generate(i+1,candidates,item,result,res_set,sum,target);
}
};
22. Generate Parentheses
題意:
輸入括號數n,輸出所有合法的括號組合的集合。
解題思路:
用遞迴的方法解:
每次加一個 ‘(’ 或者‘ ) ’,加‘)’的條件是‘(’的數目比較多。
遞迴結束條件:當字串長度達到2n
用left記錄左擴號剩餘數目
用right記錄右括號剩餘數目
每次遞迴left-1或者right-1,用條件過濾掉不滿足條件的項。
程式碼:
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> result;
generate("",n,n,result);
return result;
}
private:
void generate(string item,int left,int right,vector<string> &result){
if(left==0&&right==0) {
result.push_back(item);
return;
}
if(left>0){
generate(item+'(',left-1,right,result);
}
if(left<right){
generate(item+')',left,right-1,result);
}
}
};
51. N-Queens
題意:
在國際象棋n*n的棋盤上放置n個皇后,使它們互相不在攻擊範圍之內。
解題基礎:
方向陣列:
每放下一個皇后,它的上下左右和兩個對角線,一共8個方向都不能放置,用一個二維陣列記錄能放置的位置為0,不能的為1。如果僅僅用迴圈,程式碼比較複雜,應該用方向陣列:
static const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
dx[i],dy[i]表示一個方向,用兩個迴圈,一個迴圈遍歷所有的方向,一個迴圈遍歷方向上所有的數。
static的意思是靜態分配記憶體,就是對於經常使用的相同的常量,只分配一次空間,不用每次呼叫都分配一次記憶體。
void put_down_the_queen(int x, int y,std::vector<std::vector<int> > &mark){
static const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
mark[x][y] = 1;
for (int i = 1; i < mark.size(); i++){
for (int j = 0; j < 8; j++){
int new_x = x + i * dx[j];
int new_y = y + i * dy[j];
if (new_x >= 0 && new_x < mark.size() &&
new_y >= 0 && new_y < mark.size()){
mark[new_x][new_y] = 1;
}
}
}
}
解題思路:
資料結構:
location儲存result的圖
mark 儲存可以放的位置
步驟:
1 先初始化各個資料結構。
2 寫好放皇后的方法,放入皇后同時改變location和mark陣列
3 每次在第k行找一個位置放皇后,遍歷第k行的所有列,遞迴呼叫地找第k+1行中的皇后。
遞迴結束條件:k==n;
遞迴過程:
generate(k, location, mark,result)
用一個迴圈遍歷k行中所有列,若有為0的列,遞迴呼叫generate
遞迴前需要設定mark陣列,所以要先用臨時mark儲存mark,遞迴呼叫後還原mark,使每次迴圈的mark不變。
程式碼:
class Solution {
public:
std::vector<std::vector<std::string> > solveNQueens(int n) {
std::vector<std::vector<std::string> > result;
std::vector<std::vector<int> > mark;
std::vector<std::string> location;
for (int i = 0; i < n; i++){
mark.push_back((std::vector<int>()));
for (int j = 0; j < n; j++){
mark[i].push_back(0);
}
location.push_back("");
location[i].append(n, '.');
}
generate(0, n, location, result, mark);
return result;
}
private:
void put_down_the_queen(int x, int y,
std::vector<std::vector<int> > &mark){
static const int dx[] = {-1, 1, 0, 0, -1, -1, 1, 1};
static const int dy[] = {0, 0, -1, 1, -1, 1, -1, 1};
mark[x][y] = 1;
for (int i = 1; i < mark.size(); i++){
for (int j = 0; j < 8; j++){
int new_x = x + i * dx[j];
int new_y = y + i * dy[j];
if (new_x >= 0 && new_x < mark.size() &&
new_y >= 0 && new_y < mark.size()){
mark[new_x][new_y] = 1;
}
}
}
}
void generate(int k, int n,
std::vector<std::string> &location,
std::vector<std::vector<std::string> > &result,
std::vector<std::vector<int> > &mark){
if (k == n){
result.push_back(location);
return;
}
for (int i = 0; i < n; i++){
if (mark[k][i] == 0){
std::vector<std::vector<int> > tmp_mark = mark;
location[k][i] = 'Q';
put_down_the_queen(k, i, mark);
generate(k + 1, n, location, result, mark);
mark = tmp_mark;
location[k][i] = '.';
}
}
}
};