從大學就一直對遞歸很迷糊,想不清楚,最近刷leetcode這又是繞不過去的彎,索性這次認真研究一下並做個總結。
這裏關於回溯講解的比較容易懂。https://blog.csdn.net/versencoder/article/details/52071930
大概總結一下總有一個套路
- 定義一個全局結果用於保存最終的答案ans
- 定義一個輔助方法(函數)void backtrack(){},一般參數都有一個保存中間值的temp,其他參數根據實際題目定
- 接着分析遞歸出口,即什麼時候將中間結果temp加入ans中
還有最重要的一點就是回溯,這個是爲了還原現場,爲了不影響下一次選擇。這裏講的比較抽象,對於具體代碼怎麼體現我也迷糊了很久。下面就兩道leetcode中具體的題目講解一下。
題目一leetcode 39. combination Sum
這題在我添加的回溯法講解中講的很詳細,屬於典型的可以套用回溯的公式來。
class Solution {
public:
vector<vector<int>> ans;
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> temp;
backtracking(candidates,0,target,temp);
return ans;
}
void backtracking(vector<int>& candidates,int start,int target, vector<int> &temp){
if(target < 0) return;//湊過頭了Orz
if(target == 0){//剛好湊夠^_^
ans.push_back(temp);//加到結果中
}
else{
for(int i = start; i < candidates.size(); i++){ //循環試探每個數0.0
temp.push_back(candidates[i]);//嘗試加入=.=
backtracking(candidates,i,target-candidates[i],temp);//下一次湊的是target-candidates[i]啦~允許重複就還從i開始
temp.pop_back();//:
}
}
}
};
這麼但看這一個程序沒有問題,看不出來迷糊的地方,接着再看下一題。
leetcode 22. Generate Parentheses
這一題也是可以用回溯法,當然也可以用上面的套路實現。
class Solution {
public:
vector<vector<string>> ans;
vector<vector<string>> generateParenthesis(int n) {
vector<string> temp(n,"");
dfs(n,temp,0,0);
return ans;
}
void dfs(int n, vector<string> &temp,int left,int right){
if(temp.size()== 2*n){
ans.push_back(temp);
}
if(left < n) {
temp.push_back("(");
dfs(n,temp,left+1,right);//添加左括號的條件是左括號數量小於n
temp.pop_back();
}
if(right < left){
temp.push_back(")");
dfs(n,temp,left,right+1);//添加右括號的條件是右括號數小於左括號數
temp.pop_back();
}
}
};
這樣寫沒問題,但是看了網上的答案,有一個看起來很簡單的寫法,但是不好理解,這也是我一開始迷糊的地方。
class Solution {
public:
vector<string> ans;
vector<string> generateParenthesis(int n) {
string temp = "";
dfs(n,temp,0,0);
return ans;
}
void dfs(int n, string temp,int left,int right){
if(temp.length()== 2*n){
ans.push_back(temp);
}
if(left < n) dfs(n,temp+"(",left+1,right);//添加左括號的條件是左括號數量小於n
if(right < left) dfs(n,temp+")",left,right+1);//添加右括號的條件是右括號數小於左括號數
}
};
這裏沒有顯示的體現回退的過程,一開始不理解,主要還是對變量作用域和函數參數傳遞這些基礎知識不瞭解,在這裏因爲dfs裏面傳遞的是temp+“(”,這樣不會影響下一個分支的結果,對比上一個題的程序,之所以要回退就是因爲有for循環,就相當於下一個分支,所以在下次選擇之前要手動回退以保持之前的狀態。而在這裏在壓棧的時候每一層已經保存了一個自己的temp,所以不需要手動回退,所以回溯的思想本身都是沒有問題的,主要在於實現。