遞歸+回溯+leetcode原題講解

從大學就一直對遞歸很迷糊,想不清楚,最近刷leetcode這又是繞不過去的彎,索性這次認真研究一下並做個總結。

這裏關於回溯講解的比較容易懂。https://blog.csdn.net/versencoder/article/details/52071930

大概總結一下總有一個套路

  1. 定義一個全局結果用於保存最終的答案ans
  2. 定義一個輔助方法(函數)void backtrack(){},一般參數都有一個保存中間值的temp,其他參數根據實際題目定
  3. 接着分析遞歸出口,即什麼時候將中間結果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,所以不需要手動回退,所以回溯的思想本身都是沒有問題的,主要在於實現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章