回溯【基础算法精讲 14】

本文详细介绍了回溯算法在电话号码字母组合和子集选择问题中的应用,包括如何通过递归和子问题解决方法,以及如何处理恢复现场的问题。通过实例代码展示了如何用回溯法求解LeetCode中的相关题目。
摘要由CSDN通过智能技术生成

视频地址 : 

回溯算法套路①子集型回溯【基础算法精讲 14】_哔哩哔哩_bilibili

基本概念

1 . 例子

例如从abc和def(n = 2)中各选出一个组成新的字符串?

 如果n很大 , 这个时候for循环的表达能力有限 ;

2 . 原问题 和 子问题

 3 . 增量构造答案

这个增量构造答案的过程就是回溯的特点 ;

回溯三问

关于恢复现场的理解

在递归到某一“叶子节点”(非最后一个叶子)时,答案需要向上返回,而此时还有其他的子树(与前述节点不在同一子树)未被递归到,又由于path为全局变量。若直接返回,会将本不属于该子树的答案带上去,故需要恢复现场。
恢复现场的方式为:在递归完成后 dfs(i+1); 后,进行与“当前操作”相反的操作,“反当前操作”。 

17 . 电话号码的字母组合

这也就是n(n=8)个字符串中选两个的组合问题 ;

思路 : 

1 . 先创建一个下标 与 对应字符串映射的数组,这里使用hash表进行映射也是可以的 ;

2 . 对于回溯 , 如果到了叶子节点 , 那么就直接添加即可 , 对于每一个path[i],都是存放digits[i]中数字字符对应字符串中的一个字符 , 遍历该字符串 , 对每一个字符进行递归操作 ;

3 . 对于不用恢复现场 : 因为每次递归到 i,一定会修改 path【i】,那么递归到终点时,每个 path【i】 都被覆盖成要枚举的字母,所以不需要恢复现场。

代码实现 : 

class Solution {
public:
    const string mp[10] = {
        "",      //0
        "",     //1 
        "abc",  //2
        "def",  //3
        "ghi",  //4
        "jkl",  //5
        "mno",  //6
        "pqrs", //7
        "tuv",  //8
        "wxyz", //9
    };
    vector<char> path ;
    vector<string> ans ;
    int n ; 

    string get(){
        string tmp = "" ;
        for(char c : path){
            tmp += c ;
        }
        return tmp ;
    }

    void dfs(string digits , int i){
        // 先写递归终止条件
        if(i == n){
            ans.push_back(get()) ;
            return ; // 回溯
        }
        for(char c : mp[digits[i]-'0']){
            path[i] = c ;
            dfs(digits , i + 1) ;
            // 因为每一次path[i]都会被覆盖 , 那么就不用进行回溯
        }
    }

    vector<string> letterCombinations(string digits) {
        n = digits.size() ;
        if(n == 0) return ans ;
        path.resize(n) ;
        dfs(digits , 0) ;
        return ans ;
    }
};

更加优雅的写法 : 

class Solution {
    string MAPPING[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
public:
    vector<string> letterCombinations(string digits) {
        int n = digits.length();
        if (n == 0) return {};
        vector<string> ans;
        string path(n, 0); // 本题 path 长度固定为 n
        function<void(int)> dfs = [&](int i) {
            if (i == n) {
                ans.emplace_back(path);
                return;
            }
            for (char c : MAPPING[digits[i] - '0']) {
                path[i] = c; // 直接覆盖
                dfs(i + 1);
            }
        };
        dfs(0);
        return ans;
    }
};

78 . 子集

链接 : 

. - 力扣(LeetCode)

法一(对于每一个数选或不选) : 

class Solution {
public:
    vector<vector<int>> ans  ;
    vector<int> path ;

    void dfs(vector<int>& nums , int i){
        if(i==nums.size()){
            ans.push_back(path) ;
            return ;// 回溯
        }
        // 不选
        dfs(nums,i+1) ;// 不选i
        
        // 选
        path.push_back(nums[i]) ;
        dfs(nums,i+1) ;
        path.pop_back() ;

    }

    vector<vector<int>> subsets(vector<int>& nums) {
        if(nums.size()==0) return ans ;
        ans.clear() ;
        path.clear() ;
        dfs(nums,0) ;
        return ans ;
    }
};

法二(枚举第i个数选谁):

 这样的话  每一个结点都是答案 ;

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    void backtrack(vector<int>& nums,int idx){
        ans.push_back(path);
        if(idx >= nums.size()) return;
        for(int i=idx;i<nums.size();i++){
            path.push_back(nums[i]);
            backtrack(nums,i+1);
            path.pop_back();
        }
    }
    vector<vector<int>> subsets(vector<int>& nums) {
        path.clear();
        ans.clear();
        backtrack(nums,0);
        return ans;
    }
};

131 . 分割回文串

链接 : 

. - 力扣(LeetCode)

思路 : 

 代码 (枚举子串结束位置): 

class Solution {
public:
    bool pd(string s , int l ,int r){
        while(l <= r){
            if(s[l++] != s[r--])
                return false;
        }
        return true ;
    }

    vector<vector<string>> ans  ;
    vector<string> path ;
    int n = 0 ;  
    // 答案视角 , 判断每一个子串结束的地方

    void dfs(string s , int i){
        if(i==n){
            ans.push_back(path) ;
            return ;
        }
        for(int j=i;j<n;j++){ // 枚举子串结束的位置
            if(pd(s,i,j)){
                path.push_back(s.substr(i,j-i+1)) ;
                dfs(s,j+1) ;
                path.pop_back();// 恢复现场
            }
        }
    }


    vector<vector<string>> partition(string s) {
        n = s.size() ;
        dfs(s,0) ;
        return ans ;
    }
};

代码(每个分割点选不选) : 

class Solution {
public:
    bool pd(string s , int l ,int r){
        while(l <= r){
            if(s[l++] != s[r--])
                return false;
        }
        return true ;
    }

    vector<vector<string>> partition(string s) {
        vector<vector<string>> ans  ;
        vector<string> path ;
        // 判断每个逗号选或不选
        int n = s.size() ;
        // start 表示这一段回文串的开始位置
        function<void(int,int)> dfs = [&](int i,int start){
            if(i==n){
                ans.push_back(path) ;
                return  ;
            }
            // 不选i-i+1
            if(i < n - 1){
                dfs(i+ 1, start) ;
            }
            // 选
            if(pd(s,start,i)){
                path.push_back(s.substr(start,i-start+1));
                dfs(i + 1 , i + 1 ) ;
                path.pop_back() ;
            }
        };
        dfs(0 , 0) ;
        return ans ;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值