dfs+回溯习题整理(1)

1.LeetCode17. 电话号码的字母组合

搜索顺序:

按照给出的数字依次进行搜索,对于每一个数字枚举它选用每一个字母的方案。

回溯:

当下一个字符串已经枚举完成时,需要更新当前字符串,此时需要进行回溯。然后进行枚举,到当前字符串的下一个元素。第一次写时,循环的意义没有考虑清楚,这个做法的循环是枚举当前字符串的每个字符,而不是下一个字符串的每个字符。

class Solution {
public:
    string letter[10] = {"abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector<string> ans;
    string path, s;
    vector<string> letterCombinations(string _digits) {
        if(_digits.size() == 0) return ans;
        s = _digits;
        dfs(0, 0);
        return ans;
    }

    void dfs(int u, int y){
        if(u >= s.size()){
            ans.push_back(path);
            return ;
        }
        string& cur = letter[s[u]-'2'];
        //当前数字对应字符串的每一个字母都要遍历一遍
        for(int i = 0; i < cur.size(); i ++){
            path.push_back(cur[i]);
            //遍历下一字符串
            dfs(u+1, i);
            path.pop_back();
        }
    }
};

2.LeetCode79. 单词搜索

搜索顺序:

对矩阵中的每一个元素依次进行搜索,对每一个元素枚举可能能走的四个方向。

回溯:

考虑清楚每个循环的意义。考虑清楚何时回溯。对于本题来说,当我已经尝试走过四个方向后,都没有匹配上,进行回溯。

class Solution {
public:
    vector<vector<char>> b;
    string w;
    int dx[4] = {1, 0, -1, 0}, dy[4] = {0, 1, 0, -1};

    bool dfs(int x, int y, int u){
        //先判断字符不等的情况
        if(b[x][y] != w[u]) return false;
        //若有n-1个字母已经匹配,则成功
        if(u >= w.size()-1) return true;
        b[x][y] = '*';
        for(int i = 0; i < 4; i ++){
            int xt = x + dx[i], yt = y + dy[i];
            if(xt >= 0 && xt < b.size() && yt >= 0 && yt < b[0].size() && b[xt][yt] != '*')
                if(dfs(xt, yt, u + 1)) return true;
        }
        b[x][y] = w[u];
        return false;
    }

    bool exist(vector<vector<char>>& board, string word) {
        b = board, w = word;
        for(int i = 0; i < b.size(); i ++){
            for(int j = 0; j < b[0].size(); j ++){
                if(dfs(i, j, 0)) return true;
            }
        }
        return false;
    }
};

3.LeetCode46. 全排列

搜索顺序:

对全排列中的位置依次进行搜索,对每一个位置枚举可能能填上的数。

回溯:

当第u+1个位置的所有方案填完后,回到第u个位置枚举第u位上的所有方案,此时回溯。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> nums, path;
    vector<bool> st;
	//dfs(u)表示当前填充到第u位了
    void dfs(int u){
        if(u == nums.size()){
            ans.push_back(path);
            return ;
        }
        //枚举每个位置上的备选数字
        for(int i = 0; i < nums.size(); i ++){
            if(!st[i]){
                path.push_back(nums[i]);
                st[i] = true;
                dfs(u+1);
                path.pop_back();
                st[i] = false;
            }
        }
    }

    vector<vector<int>> permute(vector<int>& _nums) {
        nums = _nums;
        st.resize(nums.size());
        dfs(0);
        return ans;
    }
};

4.LeetCode47. 全排列 II

搜索顺序:

将相同的元素都集中到一起,在填充位置时保证相同元素的相对位置不变。所以上一题dfs是对填充的位置进行搜索,对填充位置的数组元素进行枚举,本题dfs为保证相同元素的相对位置不变,对数组元素依次进行搜索,对存放数组的位置进行枚举。如果下一个递归的元素等于上一个递归的元素,则下一个递归的元素只能放在当前元素存放位置的后面。

回溯:

数组中第u+1个数枚举完所有位置时进行回溯。

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path, nums;
    vector<bool> st;
    unordered_map<int, int> pos;

    //dfs表示用数组中第u个元素进行填充,填充从第t个位置开始
    void dfs(int u, int t){
        if(u == nums.size()){
            ans.push_back(path);
            return ;
        }
        //枚举能被填充的各个位置
        for(int i = t; i < nums.size(); i ++){
            //当位置i还没被使用过
            if(!st[i]){
                path[i] = nums[u];
                st[i] = true;
                //开始填充用第u+1位元素,如果下一位元素与当前元素相同需要放到第i位后面
                //不同则从0号位置开始找空闲
                dfs(u+1, u+1<nums.size() && nums[u+1] == nums[u] ? i+1 : 0);
                st[i] = false;
                //path下次递归可被自动覆盖
            }
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& _nums) {
        nums = _nums;
        st.resize(nums.size()), path.resize(nums.size());
        sort(nums.begin(), nums.end());
        dfs(0, 0);
        return ans;
    }
};

5.LeetCode78. 子集

搜索顺序:

对数组中的元素依次进行搜索,对每一个元素可能加入集合,也可能不加。

回溯:

第u+1个元素的两种情况枚举完后,回溯。

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

    //对第u个位置上的元素进行选择
    void dfs(int u){
        if(u == nums.size()){
            ans.push_back(path);
            return ;
        }
        //不放入,直接进入第u+1个位置
        dfs(u + 1);
        //将第u个位置上的元素放入子集
        path.push_back(nums[u]);
        dfs(u + 1);
        path.pop_back();
    }

    vector<vector<int>> subsets(vector<int>& _nums) {
        nums = _nums;
        dfs(0);
        return ans;
    }
};

用二进制数来表示第j位是否选择加入子集

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

6.LeetCode90. 子集 II

搜索顺序:

在上一题的基础上推广,对数组中的元素依次进行搜索,同一个元素选择0~k次(k为相同元素的数量)。

回溯:

第u+1个元素选择0~k次的所有情况枚举完后,回溯。

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

    void dfs(int u){
        if(u == nums.size()){
            ans.push_back(path);
            return ;
        }
        int k = 0;
        while(u+k <nums.size() && nums[u+k] == nums[u]) k++;
        //枚举第u个元素选择从0到k的不同情况
        for(int i = 0; i <= k; i ++){
            dfs(u+k);
            path.push_back(nums[u]);
        }
        //恢复现场
        for(int i = 0; i <= k; i ++) path.pop_back();
    }

    vector<vector<int>> subsetsWithDup(vector<int>& _nums) {
        nums = _nums;
        sort(nums.begin(), nums.end());
        dfs(0);
        return ans;
    }
};

7.LeetCode216. 组合总和 III

搜索顺序:

按位置搜索,每一个位置都可填入在0~9之内,并比前一个数大的数。

回溯:

下一位置枚举完所有可能数后回溯。

class Solution {
public:
    vector<int> res;
    vector<vector<int>> ans;
    int k, n;
    //枚举到了第kt个数字,当前总和为nt,选择比t大的数
    void dfs(int kt, int nt, int t){
        if(k == kt){
            if(nt == n) ans.push_back(res);
            return ;
        }else if(kt > k || nt > n) return ;
        for(int i = t + 1; i <= 9; i ++){
            res.push_back(i);
            dfs(kt+1, nt+i, i);
            res.pop_back();
        }
    }
    
    vector<vector<int>> combinationSum3(int _k, int _n) {
        k = _k, n = _n;
        dfs(0, 0, 0);
        return ans;
    }
};

8.LeetCode52. N皇后 II

搜索顺序:

按行搜索,枚举每一列上可能的元素。

回溯:

下一行枚举完回溯。

class Solution {
public:
    //列、对角线、反对角线是否放上皇后
    vector<bool> col, dg, udg;
    int ans, n;

    //按行递归,在第u行上给皇后选位置
    void dfs(int x){
        if(x == n){
            ans ++;
            return ;
        }
        //枚举每列是否能放上皇后
        for(int y = 0; y < n; y ++){
            if(!col[y] && !dg[x-y+n] && !udg[x+y]){
                col[y] = dg[x-y+n] =  udg[x+y] = true;
                dfs(x + 1);
                col[y] = dg[x-y+n] =  udg[x+y] = false;
            }
        }
    }

    int totalNQueens(int _n) {
        n = _n;
        col.resize(n), dg.resize(2*n), udg.resize(2*n);
        dfs(0);
        return ans;
    }
};

9.LeetCode37. 解数独

搜索顺序:

从左到右,从上到下依次搜索。

回溯:

如果下一次搜索没有解,回溯。

class Solution {
public:

    //每一行(列 or 单元格)的九个数字是否使用过
    bool row[9][9], col[9][9], cell[3][3][9];

    bool dfs(int x, int y, vector<vector<char>>& b){
        if(y == 9) x ++, y = 0;
        if(x == 9) return true;
        if(b[x][y] == '.'){
            for(int i = 0; i < 9; i ++){
                if(!row[x][i] && !col[y][i] && !cell[x/3][y/3][i]){
                    row[x][i] = col[y][i] = cell[x/3][y/3][i] = true;
                    b[x][y] = '1' + i; 
                    if(dfs(x, y + 1, b)) return true;
                    row[x][i] = col[y][i] = cell[x/3][y/3][i] = false;
                    b[x][y] = '.';
                }
            }
        }
        else return dfs(x, y + 1, b);
        return false;
    }

    void solveSudoku(vector<vector<char>>& board) {
        for(int i = 0; i < 9; i ++){
            for(int j = 0; j < 9; j ++){
                char c = board[i][j];
                if(c != '.')
                    row[i][c-'1'] = col[j][c-'1'] = cell[i/3][j/3][c-'1'] = true;
            }
        }
        dfs(0, 0, board);
    }
};

10.LeetCode473. 火柴拼正方形

搜索顺序:

依次拼接正方形的每条边。每条边枚举木棒顺序从长到短(木棒越长,dfs的分支越少,分支下的方案数就越多,剪枝的效果就越好。

剪枝:

1、所有木棒长度的总和不是4的倍数或木棒不到4根。
2、如果当前木棒拼接失败,则跳过接下来所有长度相同的木棒。
3、如果当前木棒拼接失败,且是当前边的第一个,则直接剪掉当前分支。
4、如果当前木棒拼接失败,且是当前边的最后一个,则直接剪掉当前分支。

class Solution {
public:
    int length;
    vector<bool> st;
    vector<int> nums;

    bool makesquare(vector<int>& _nums) {
        nums = _nums;
        int sum = 0;
        for (auto num : nums) sum += num;
        if (nums.size() < 4 || sum % 4) return false;
        length = sum / 4;
        sort(nums.begin(), nums.end());
        reverse(nums.begin(), nums.end());

        st.resize(nums.size());

        return dfs(0, 0, 0);
    }

    //拼接第u根木棒,当前长度为cur,从第start棵木棒开始选
    bool dfs(int u, int cur, int start)
    {
        if (u == 4) return true;
        if (cur == length) return dfs(u + 1, 0, 0);

        for (int i = start; i < nums.size(); i ++ )
            if (!st[i] && cur + nums[i] <= length)
            {
                st[i] = true;
                if (dfs(u, cur + nums[i], i + 1)) return true;
                st[i] = false;

                while (i + 1 < nums.size() && nums[i + 1] == nums[i]) i ++ ;
                if (!cur) return false;
                if (cur + nums[i] == length) return false;
            }

        return false;
    }
};

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值