常见的回溯算法题总结

回溯算法

所有的回溯法解决问题可以抽象为树形结构

DFS模板

image-20240218155414007




BFS模板

image-20240218155458508



image-20240218155107693



image-20240218155131421



组合

vector.pop_back()

image-20240218163242171



还可以剪枝

在求和问题中,排序之后加剪枝是常见的套路!

image-20240218165331120



组合求和III

image-20240218170323225

优化后的代码

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

    void DFS(int a, int k, int n, int sum){
        if(path.size() >= k){   //其实只会== ,>=更健壮一点
            if(sum > n){
                return ;
            }
            if(sum == n){
                arr.push_back(path);
            }
            return ;
        }
        for(int i=a;i<=9-(k-path.size())+1;i++){   //剪枝  不要忘了加一
            path.push_back(i);
            DFS(i+1,k,n,sum+i);  //sum+i直接放到参数里,也是另一种意义上的回溯
            path.pop_back();
        }
    }
    vector<vector<int>> combinationSum3(int k, int n) {
        DFS(1,k,n,0);
        return arr;
    }
};


电话号码的字母组合





和上面两道题不一样的地方

image-20240218205357559



这句话得慢慢品

其实for就是当前能走的选择,在递归就是迈出那一步

image-20240218205552899



组合总和

image-20240219101828590

优化如下

image-20240218213056070



**组合总数

难点

image-20240218224939281

去重思路

名词:树层去重 和 树枝去重

这里是树层去重

image-20240218225208020





image-20240218224749829

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;
    int used[105];
    long long sum = 0; 

    void DFS(int ind, vector<int>& arr, int tar){
        if(sum > tar) return ;
        if( sum == tar){
            ans.push_back(path);
            return ;
        }
        for(int i=ind;i<arr.size();i++){
            if(i > 0 && arr[i] == arr[i-1] && used[i-1] == 0 ){  //树枝去重!
                continue;
            } 
            path.push_back(arr[i]);
            sum += arr[i];
            used[i] = 1;  //标记使用过
            DFS(i+1, arr, tar);
            path.pop_back();
            sum -= arr[i];
            used[i] = 0;
        }
    }
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        for(int i = 0;i<100;i++) used[i] = 0;
        sort(candidates.begin(), candidates.end());  //排序是为了让相等的元素靠在一起
        DFS(0, candidates, target);
        return ans;
    }
};


关于 const string&

image-20240220114657972

所以我们无脑直接加吧



分割回文串

切割问题

好像遇到不懂的回溯题,画个树形图会跟好理解

难点

  • 如何切割子串 – ind 到 i;

image-20240220125810742



注意substr函数的参数



画图就好理解多了

image-20240220125952797

在这里插入图片描述



复原IP地址

和上一题很像,都是切割问题

image-20240225231232299

class Solution {
public:
    vector<string> ans;
    string path;
    int cnt = 0;

    int work(const string& s){
        if(s.size() > 3) return 0;
        int val = 0, t = 1;
        for(int i=s.size()-1;i>=0;i--){
            val += t*(s[i] - '0');
            t *= 10;
        }
        if(val > 255) return 0;
        if(s.size() > 1 && s[0] == '0') return 0;
    return 1;

    }
    void DFS(int ind, const string& s){
        if(ind >= s.size() && cnt == 4){  //切割到末尾 & 正好是4段
            ans.push_back(path);
            return;
        }

        for(int i=ind; i<s.size(); i++){
            string ss = s.substr(ind,i-ind+1);
            string oldp;   //用于回溯
            if( work(ss) == 1){
                cnt++;   //统计分成了几段
                oldp = path;
                if(path.size() != 0) path.push_back('.');
                for(int j=0; j<ss.size();j++)
                   path.push_back(ss[j]);
            }else {
                continue;
            }
            DFS(i+1, s);
            path = oldp;
            cnt--;
        }
    }
    vector<string> restoreIpAddresses(string s) {
        if (s.size() < 4 || s.size() > 12) return ans; // 算是剪枝了
        DFS(0, s);
        return ans;
    }
};



子集

送分题

子集问题,在树形结构中子集问题是要收集所有节点的结果,

而组合问题是收集叶子节点的结果

image-20240226172126411



子集II

树层去重 + 子集

image-20240227114452187



非递减子序列

关键是在不排序的情况下如何去重

这也是需要注意的点,unordered_set<int> uset; 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!

刚刚测试了一下,之前排序后的数组使用used的方法 可以 用 sett去重来替代 – 不过set的效率肯定手没有数组快的

借助set – set.find() == set.end(), set.insert()

image-20240227134515253



全排列

其实参数那里可以不用ind, 但是加上也不影响

image-20240227135853146



全排列II

从两个方面去重

一个是树枝去重,一个是全排列去重

image-20240227142357959



老师的代码

其实一个用used数组就可以了

image-20240227142545091



时间复杂度

image-20240227144414803

.



关于集合内值的排序

image-20240302170048782

重新安排行程

难点:

  • 字母排序
  • 设计数据结构
  • 如何遍历 – 增强for循环
  • pair的用法

只有一个答案的时候且拿路径作为答案返回的时候,要小心答案不要被回溯了

image-20240302191833380

class Solution {
public:
    //  起飞位置    到达位置  次数
    map<string, map<string, int>> mapp; //对应key相同的元素,自动按字母升序排序
    vector<string> path;
    int mark = 0;

    void DFS(int cnt){
        if(mark == 1) return ;
        if(path.size() >= cnt + 1 ){
            mark = 1;
            return ;
        }

        //遍历
        for(pair<const string, int>& target : mapp[path.back()]){  //path里面最后一个元素就是下一个航班的起飞位置
            if(target.second > 0){  //是否还有次数
                path.push_back(target.first);
                target.second--;
                DFS(cnt);
                if(mark == 1) return ; //一定要加上这个 ,不然答案记录答案后 被后面的逻辑给回溯了 就没有了
                path.pop_back();
                target.second++; 
            }
        }
    }
    vector<string> findItinerary(vector<vector<string>>& tickets) {
        //对次数进行预处理
        for(vector<string>& vec : tickets ){
            mapp[vec[0]][vec[1]]++;
        }
        //从JFK开始飞
         path.push_back("JFK");

         DFS(tickets.size());

         return path;

    }
};


N皇后
  • 初始化 vector path(n,string(n,‘.’));

  • 理解path,和ans的关系 – 一定要提前想清楚

    如何初始化
    vector path(n,string(n,‘.’));

<img src="https://gitee.com/shi-keyou/picgo-image/raw/master/image-20240302203716640.png />

class Solution {
public:
    vector<vector<string>> ans;

    int work(int n, int han, int lei, vector<string>& path ){
        //判断垂直方向
        for(int i = han -1; ;i--){
            if(i < 0) break;
            if(path[i][lei] == 'Q') return 0;
        }

        //左上方
        for(int i = han-1, j = lei-1; ;i--,j--){
            if(i < 0 || j < 0) break;
            if(path[i][j] == 'Q') return 0;
        }

        //右上方
        for(int i = han-1, j = lei+1; ; i--,j++){
            if(i < 0 || j >= n) break;
            if(path[i][j] == 'Q') return 0;
        }
        return 1;
    }
    void DFS(int n,int han, vector<string>& path ){
        if(han >= n){
            ans.push_back(path);
            return ;
        }

        for(int lei = 0; lei < n; lei++){
            if(work(n,han,lei,path) == 1){
                path[han][lei] = 'Q';
                DFS(n,han+1,path);
                path[han][lei] = '.';
            }
        }
    }
    vector<vector<string>> solveNQueens(int n) {
        //初始化
        vector<string> path(n,string(n,'.'));
        DFS(n,0,path);
        return ans;
    }
};


解数独

难点

  • 如何判断是否遵循条件
  • 如何递归 – 二维递归
image-20240302223732666
class Solution {
public:
    int mark = 0;

    int work(int han,int lei, vector<vector<char>>& board){
        //垂直方向
        for(int i = 0;i<board.size(); i++){
                if(i == han) continue;
                if(board[i][lei] == board[han][lei]) return 0;
        }
        //水平
        for(int i = 0;i<board.size(); i++){
                if(i == lei) continue;
                if(board[han][i] == board[han][lei]) return 0;
        }

        //小正方形 -- 数学问题 ,找左上角
        int x = (han/3) * 3;
        int y = (lei/3) * 3;
        for(int i = x; i<x+3; i++){
            for(int j = y; j<y+3; j++){
                if( i == han && j == lei) continue;
                if(board[i][j] ==  board[han][lei]) return 0;
            }
        }
        return 1;
    }
    void DFS(vector<vector<char>>& board){
        if(mark == 1) return ;
        //因为每一层不止修改一个元素 所以我们这里 二维递归
        for(int i=0;i<board.size();i++){
            for(int j=0;j<board[0].size();j++){
                if(board[i][j] == '.'){
                    for(int k=1;k<=9;k++){
                        board[i][j] = '0' + k;
                        if(work(i,j,board) == 1){
                            DFS(board);
                            if(mark == 1) return;  //防止找到答案后被回溯了 

                        }
                        board[i][j] = '.';
                    }
                    return ;
                }
            }
        }
        mark = 1;
        return ;
    }
    void solveSudoku(vector<vector<char>>& board) {
        DFS(board);
    }
};


回溯大总结

<img src="https://gitee.com/shi-keyou/picgo-image/raw/master/image-20240302224218492.png/>



常见剪枝



什么时候需要startindex

image-20240302224521265



去重方式有两种

在使用used去重时一定要先排序

image-20240302224756753



image-20240302224914997



思维导图

img






此文章用于笔者记录学习,也希望对你有帮助

  • 24
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值