LeetCode刷题——回溯法(C/C++)

[中等]全排列

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> num;
    int flag[10];
    vector<vector<int>> permute(vector<int>& nums) {
        recall(nums);
        return ans;
    }
    void recall(vector<int>& nums) {
        if (num.size() == nums.size()) {
            ans.push_back(num);
        }
        for (int i = 0; i < nums.size(); i++) {
            if (flag[i] == 0) {
                flag[i] = 1;
                num.push_back(nums[i]);
                recall(nums);
                flag[i] = 0;
                num.pop_back();
            }
        }   
    }
};

[中等]全排列 II

  • 原题链接
  • 题解
    在全排列问题的回溯法题解基础上,加上剪枝的思路,先对nums中的数字进行排列,则重复的数字会靠在一起,例如2,2,2,2,保证他们按照固定的顺序被选中,则判断思路是,如果前一个数字与当前数字相同,且前一个数字还没有被选,则排除这个分支。也就是使用continue语句跳过目前这个分支
vector<vector<int>> ans;
    vector<int> num;
    int flag[10];
    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(),nums.end());
        recall(nums);
        return ans;
    }
    void recall(vector<int>& nums) {
        if (num.size() == nums.size()) {
            ans.push_back(num);
        }
        for (int i = 0; i < nums.size(); i++) {
            if (flag[i] == 0) {//这个数还没取过
                if (i-1>=0) //存在前一个数
                    if (flag[i-1]==0 && nums[i-1]==nums[i])//前一个数与当前数相同且还没取过
                        continue;
                flag[i] = 1;
                num.push_back(nums[i]);
                recall(nums);
                flag[i] = 0;
                num.pop_back();
            }
        }
    }

[中等]组合

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> num;
    vector<vector<int>> combine(int n, int k) {
        recall(n,k,1);
        return ans;
    }
    /*
    p表示当前递归的层数,剪枝的思路是只选择大于p的数
    */
    void recall(int n,int k, int p){
        if(num.size() == k){
            ans.push_back(num);
            return;
        }
        for(int i=p;i<=n;i++){
            num.push_back(i);
            recall(n,k,i+1);
            num.pop_back();//恢复现场
        }
    }
};

[中等]组合总和

  • 原题链接
  • 题解
    涉及剪枝问题,其实动手画一下回溯路径就比较好理解了
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> num;
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        recall(candidates,0,target,0);
        return ans;
    }
    void recall(vector<int>& nums,int sum,int target, int k) {
        if (sum == target) {
            ans.push_back(num);
        }
        else if(sum > target){
            return;
        }
        for (int i = k; i < nums.size(); i++) {
            num.push_back(nums[i]);
            sum += nums[i];
            recall(nums,sum,target,i);
            sum -= nums[i];
            num.pop_back();
        }
    }
};

[中等]组合总和 II

  • 原题链接
  • 题解
    就是前面的全排列 II组合总和两个问题的剪枝思路的合并,给传入的参数排序之后
    1.对相同的数字,要按照原先的顺序选取
    2.限制同组合的反复选取,比如[1,2,5] 在第一个分支中就取到,之后取了2之后就不在考虑取1,因为选取元素[1,2]与选取元素[2,1]从集合角度考虑并无不同。
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> num;
    int flag[110];
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        sort(candidates.begin(),candidates.end());
        recall(candidates,target,0,0);
        return ans;
    }
    void recall(vector<int>& nums, int target, int sum, int k) {
        if (sum == target) {
            ans.push_back(num);
        }
        else if(sum>target){
            return;
        }
        for (int i = k; i < nums.size(); i++) {
            if (flag[i] == 0) {//这个数还没取过
                if (i-1>=0) //存在前一个数
                    if (flag[i-1]==0 && nums[i-1]==nums[i])//前一个数与当前数相同且还没取过
                        continue;
                flag[i] = 1;
                sum += nums[i];
                num.push_back(nums[i]);
                recall(nums,target,sum,i);
                flag[i] = 0;
                sum -= nums[i];
                num.pop_back();
            }
        }
    }
};

[中等]复原 IP 地址

  • 原题链接
  • 题解
    这题卡了很久
    ①字符串的操作要熟练一点,包括substr(),earse(),insert()的用法
    ②在回溯法的循环中记得要加上判断i < s.size() && i < f + 3:每一个子段最多3位,并且不能越界
class Solution {
public:
    vector<string> ans;
    vector<string> restoreIpAddresses(string s) {
        string str = "";
        recall(s, 0, 0, str);//从第一段开始回溯;
        return ans;
    }

    //回溯方法,递归调用
    //f表示现在遍历到数组的第几个元素
    //k表示当前的层数,因为ip地址就分四段,分到第四段就需要进行判断
    //str表示一组答案
    void recall(string s, int f, int k, string str) {
        if (k == 4) {
            if (f != s.size()) return;
            str.erase(str.size() - 1, 1);//去掉.
            ans.push_back(str);
            str.insert(str.size(), 1, '.');//把.加回去,保留现场
            return;
        }
        else if (f == s.size()) return;
        for (int i = f; i < s.size() && i < f + 3; i++) {
            if (lawful(s, f, i)) {
                str.insert(str.size(), s, f, i - f + 1);
                str.insert(str.size(), 1, '.');
                recall(s, i + 1, k + 1, str);
                str.erase(str.size() - (i - f + 2), i - f + 2);
            }
        }
    }

    //判断字符串是否为ip地址的合法数字
    bool lawful(string s, int begin, int end) {
        string flag = s.substr(begin, end - begin + 1);
        if (flag.size() > 1 && flag[0] == '0') return false;
        stringstream strs;
        int num;
        strs << flag;
        strs >> num;
        if (num > 255) return false;
        return true;
    }
};

[中等]括号生成

  • 原题链接
  • 题解
    leftk表示(还可以添加的个数,rightk表示)还可以添加的个数,并且rightk要大于等于leftk
class Solution {
public:
    vector<string> ans;
    vector<string> generateParenthesis(int n) {
        string str;
        recall(str,n,n);
        return ans;
    }

    /*
    leftk表示(还可以添加的个数
    rightk表示)还可以添加的个数
    并且rightk要大于等于leftk
    */
    void recall(string str, int leftk ,int rightk){
        if(leftk ==0 && rightk==0){
            ans.push_back(str);
        }
        if(leftk>0){
            str += '(';
            recall(str,leftk-1,rightk);
            str.erase(str.size() - 1, 1);//恢复现场
        }
        if(rightk>leftk && rightk>0){
            str += ')';
            recall(str,leftk,rightk-1);
            str.erase(str.size() - 1, 1);//恢复现场
        }
    } 
};

[中等]子集

  • 原题链接
  • 题解
    比较简单的回溯法,剪枝的主要思路就是保证递归循环之中选的数在k之后。
class Solution {
public:
    vector<vector<int>> ans;
    vector<int> num;
    vector<vector<int>> subsets(vector<int>& nums) {
        ans.push_back(num);
        sort(nums.begin(),nums.end());
        recall(nums,0);
        return ans;
    }
    void recall(vector<int>& nums,int k){
        for(int i=0;i<nums.size();i++){
            if(i>=k){
                num.push_back(nums[i]);
                ans.push_back(num);
                recall(nums,i+1);
                num.pop_back();
            }
        }
    }
};

[中等]单词搜索

  • 原题链接
  • 题解
    ①调用recall()回溯函数的时候,每一步return最好是设置当前标记为0flag[i][j]=0,恢复现场
    ②如果你在return true的时候觉得可以不返回现场,又把flag数组设置为全局变量,可能会出现力扣和vs运行情况不同的问题,因为力扣同时会测多组数据,恢复现场没有做好,容易使全局中的flag标记没有回到清零的状态,会使第一个样例之后的部分样例结果错误。
class Solution {
public:
    int flag[7][7];
    bool exist(vector<vector<char>>& board, string word) {    
        //找出所有元素为word[0]的点,标记为flag = 1
        for (int i = 0; i < board.size(); i++)
            for (int j = 0; j < board[0].size(); j++)
                if (board[i][j] == word[0]) {
                    if (recall(board, word, 0, i, j)) return true;
                }
        return false;
    }
    bool recall(vector<vector<char>>& board, string word, int k, int x, int y) {
        //表示走过
        flag[x][y] = 1;
        if (board[x][y] != word[k]) {
            flag[x][y] = 0;
            return false;
        }
        //成功的返回条件
        if (k == word.size()-1){
            flag[x][y] = 0;
            return true;
        }
        //往上走
        if (x > 0 && !flag[x - 1][y]) {
            if (recall(board, word, k + 1, x - 1, y)) {
                flag[x][y] = 0;
                return true;
            }
        }
        //往下走
        if (x < board.size() - 1 && !flag[x + 1][y]) {
            if (recall(board, word, k + 1, x + 1, y)) {
                flag[x][y] = 0;
                return true;
            }
        }
        //往左走
        if (y > 0 && !flag[x][y - 1]) {
            if (recall(board, word, k + 1, x, y - 1)) {
                flag[x][y] = 0;
                return true;
            }
        }
        //往下走
        if (y < board[x].size() - 1 && !flag[x][y + 1]) {
            if (recall(board, word, k + 1, x, y + 1)) {
                flag[x][y] = 0;
                return true;
            }
        }
        flag[x][y] = 0;
        return false;
    }
};

[困难]N 皇后

  • 原题链接
  • 题解
    放置皇后主要有四种需要判断的情况
    ①同一行仅一个
    ②同一列仅一个
    ③同一正对角线仅一个
    ④同一副对角线仅一个
    递归的时候按逐行操作的方式递归,避免了同一行放置皇后,同时在每行的循环中,对同一列、主对角线、副对角线的情况进行判断。

请添加图片描述

class Solution {
public:
    vector<vector<string>> ans;
    vector<vector<string>> solveNQueens(int n) {
        string str = "";
        for(int i=0;i<n;i++) str+=".";
        vector<string> board(n,str);
        dfs(board,0,n);
        return ans;
    }

    //先按行递归,保证行不冲突
    void dfs(vector<string> board, int k,int n){
        //递归出口
        if(k==n){
            ans.push_back(board);
            return;
        }
        for(int j=0;j<n;j++){
            if(board[k][j] == '.'){
                int flag= 0;
                int line1 = k-j;//正对角线参数  行-列=line1 列=行-line1
                int line2 = k+j;//副对角线参数  行+列=line2 列=line2-行
                for(int i=k;i>=0;i--){
                    if(board[i][j] == 'Q') {flag = 1; break;}//验证同一列
                    if(i - line1 >=0 && board[i][i - line1] == 'Q') {flag = 1; break;}//验证正对角线
                    if(line2 - i <n && board[i][line2 - i] == 'Q') {flag = 1; break;}//验证副对角线
                }
                if(!flag){
                    board[k][j] = 'Q';
                    dfs(board,k+1,n);
                    board[k][j] = '.';//恢复现场
                }
            }
        }
        
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值