回溯问题

参考资料:回溯算法套路

一、子集型回溯

17.电话号码的字母组合

class Solution {
public:
    string mp[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    vector<string> ans;

    vector<string> letterCombinations(string digits) {
        int n = digits.size();
        if (n == 0) return {};
        string p(n, 0);
        function<void(int)> dfs = [&](int u) {//当前枚举到第u位
            if (u == n) {//枚举到最后一位的后一位,说明选择字符结束,将
                ans.push_back(p);
                return;
            }
            for (auto c : mp[digits[u] - '0']) {
                p[u] = c;//直接覆盖
                dfs(u + 1);//枚举下一位
            }
        };
        dfs(0);//从第0位开始枚举
        return ans;
    }
};

78.子集

思考角度一、考虑每个数选或不选,这样枚举到 \(i\) = \(n\) 时说明所有情况考虑完毕,退出 \(\rm dfs\)

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> ans;
        if (n == 0) return {};
        vector<int> p;
        function<void(int)> dfs = [&](int i) {//枚举第i个数选或不选
            if (i == n) {
                ans.push_back(p);
                return;
            }
            dfs(i + 1);//不选这个数,直接跳过,枚举下一个数
            p.push_back(nums[i]);//选这个数
            dfs(i + 1);
            p.pop_back();//恢复现场
        };
        dfs(0);
        return ans;
    }
};

思考角度二、
答案的角度考虑:枚举第 \(i\) 个位置应该选原数组中的哪个数。(\(i\) 表示枚举的起点)

image

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        int n = nums.size();
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int)> dfs = [&](int i) {
            ans.push_back(p);
            if (i == n) return;//由于i = n时不进入循环,这行可以省略
            for (int j = i; j < n; j++) {
                p.push_back(nums[j]);
                dfs(j + 1);
                p.pop_back();
            }         
        };
        dfs(0);
        return ans;
    }
};

131.分割回文串

思考角度一、假设每对相邻字符之间有个逗号,那么就看每个逗号是选还是不选。

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

    vector<vector<string>> partition(string s) {
        vector<vector<string>> ans;
        vector<string> p;
        int n = s.size();
        function<void(int, int)> dfs = [&](int i, int u) {//i表示起始位置,u表示当前枚举到的位置
            if (u == n) {
                ans.push_back(p);
                return;
            }
            if (u != n - 1) dfs(i, u + 1);
            if (is(s, i, u)) {
                p.push_back(s.substr(i, u - i + 1));
                dfs(u + 1, u + 1);
                p.pop_back();
            }
        };
        dfs(0, 0);
        return ans;
    }
};

思考角度二、依次枚举每个字串结束(逗号)的具体位置

image

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

    vector<vector<string>> partition(string s) {
        int n = s.size();
        vector<vector<string>> ans;
        vector<string> p;
        function<void(int)> dfs = [&](int i) {
            if (i == n) {
                ans.push_back(p);
                return;
            }
            for (int j = i; j < n; j++) {
                if (is(s, i, j)) {
                    p.push_back(s.substr(i, j - i + 1));
                    dfs(j + 1);
                    p.pop_back();
                }
            }
        };
        dfs(0);
        return ans;
    }
};

257.二叉树的所有路径

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<string> ans;

    void dfs(TreeNode* t, string s) {
        if (!t) return;
        s += to_string(t->val);
        if (t->left == nullptr && t->right == nullptr) {
            ans.push_back(s);
            return;
        }
        s += "->";
        dfs(t->left, s);
        dfs(t->right, s);
    }

    vector<string> binaryTreePaths(TreeNode* root) {
        if (!root) return ans;
        dfs(root, "");
        return ans;
    }
};

784.字母大小写全排列

class Solution {
public:
    vector<string> letterCasePermutation(string s) {
        int n = s.size();
        string p(n, '0');
        vector<string> ans;
        function<void(int)> dfs = [&](int i) {
            if (i == n) {
                ans.push_back(p);
                return;
            }
            p[i] = s[i];
            dfs(i + 1);
            if (isalpha(s[i])) {
                p[i] = s[i] ^ ' ';
                dfs(i + 1);
            }
        };
        dfs(0);
        return ans;
    }
};

LCP 51.烹饪料理

思考角度一、枚举第 \(i\) 个料理选或不选

class Solution {
public:
    bool check(vector<vector<int>>& cookbooks, vector<int>& materials, vector<vector<int>>& attribute, int i) {
        for (int j = 0; j < materials.size(); j++) {
            if (materials[j] < cookbooks[i][j]) return false;
        }
        return true;
    }

    int perfectMenu(vector<int>& materials, vector<vector<int>>& cookbooks, vector<vector<int>>& attribute, int limit) {
        int n = materials.size();//n = 5,食材种数
        int m = cookbooks.size();//料理种数
        int ans = -1, feel = 0, taste = 0;
        function<void(int)> dfs = [&](int i) {
            if (i == m) {
                if (feel >= limit) {
                    ans = max(ans, taste);
                }
                return;
            }
            dfs(i + 1);//不选第i种
            if (check(cookbooks, materials, attribute, i)) {//选择第i种
                for (int j = 0; j < materials.size(); j++) {
                    materials[j] -= cookbooks[i][j];
                }
                feel += attribute[i][1];
                taste += attribute[i][0];
                dfs(i + 1);
                for (int j = 0; j < materials.size(); j++) {
                    materials[j] += cookbooks[i][j];
                }
                feel -= attribute[i][1];
                taste -= attribute[i][0];
            }
        };
        dfs(0);
        return ans;
    }
};

思考角度二、从答案的角度,枚举选哪个

class Solution {
public:
    bool check(vector<vector<int>>& cookbooks, vector<int>& materials, vector<vector<int>>& attribute, int i) {
        for (int j = 0; j < materials.size(); j++) {
            if (materials[j] < cookbooks[i][j]) return false;
        }
        return true;
    }

    int perfectMenu(vector<int>& materials, vector<vector<int>>& cookbooks, vector<vector<int>>& attribute, int limit) {
        int n = materials.size();//n = 5,食材种数
        int m = cookbooks.size();//料理种数
        int ans = -1, feel = 0, taste = 0;
		//枚举未考虑过的每一种料理
        function<void(int)> dfs = [&](int i) {
            if (feel >= limit) {
                ans = max(ans, taste);
            }
            if (i == m) return;//可以省去
            for (int k = i; k < m; k++) {
                if (check(cookbooks, materials, attribute, k)) {
                    for (int j = 0; j < materials.size(); j++) {
                        materials[j] -= cookbooks[k][j];
                    }
                    feel += attribute[k][1];
                    taste += attribute[k][0];
                    dfs(k + 1);
                    for (int j = 0; j < materials.size(); j++) {
                        materials[j] += cookbooks[k][j];
                    }
                    feel -= attribute[k][1];
                    taste -= attribute[k][0];
                }
            }
        };
        dfs(0);
        return ans;
    }
};

二、组合型回溯

77.组合

法一:枚举下一个数选哪个

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int)> dfs = [&](int i) {
            int d = k - p.size(); //还需要选d个数
            if (d == 0) { //选好了
                ans.push_back(p);
                return;
            }
            if (i < d) return;//也可以去掉这行,直接把循环条件写成j >= d;
            for (int j = i; j >= 1; j--) {
                p.push_back(j);
                dfs(j - 1);
                p.pop_back();
            }
        };  
        dfs(n);
        return ans;
    }
};

法二:从选和不选的角度

class Solution {
public:
    vector<vector<int>> combine(int n, int k) {
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int)> dfs = [&](int i) { // [1, n]
            int d = k - p.size();
            if (i < d) return;
            if (d == 0) {
                ans.push_back(p);
                return;
            }
            dfs(i - 1);//不选

            p.push_back(i);//选
            dfs(i - 1);
            p.pop_back();
        };
        dfs(n);
        return ans;
    }
};

216.组合总和Ⅲ

image

法一、从选或不选的角度:

class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ans;
        vector<int> p;

        function<void(int, int)> dfs = [&](int i, int t) {
            int d = k - p.size();
            if (i < d || t < 0 || t > (i * 2 - d + 1) * d / 2) // 剪枝
                 return;
            if (d == 0) { //当d = 0时,且能走到这说明不满足剪枝条件t < 0 || t > 0,则 t = 0,满足条件。所以无需单独判断t = 0;
                ans.push_back(p);
                return;
            }
            dfs(i - 1, t);//不选

            p.push_back(i);
            dfs(i - 1, t - i);//选
            p.pop_back();
        };
        dfs(9, n);
        return ans;
    }
};

法二、枚举选哪个数

class Solution {
public:
    vector<vector<int>> combinationSum3(int k, int n) {
        vector<vector<int>> ans;
        vector<int> p;

        function<void(int, int)> dfs = [&](int i, int t) {
            int d = k - p.size();
            if (i < d || t < 0 || t > (i * 2 - d + 1) * d / 2) // 剪枝
                 return;
            if (d == 0) {
                ans.push_back(p);
                return;
            }
            for (int j = i; j >= 1; j--) {
                p.push_back(j);
                dfs(j - 1, t - j);
                p.pop_back();
            }
        };
        dfs(9, n);
        return ans;
    }
};

22.括号生成

class Solution {
public:
    vector<string> generateParenthesis(int n) {
        vector<string> ans;
        string p(n * 2, '0');
        function<void(int, int)> dfs = [&](int i, int left) -> void {
            if (i == n * 2) {
                ans.push_back(p);
                return;
            }
            if (left < n) {//可以填左括号
                p[i] = '(';
                dfs(i + 1, left + 1);//左括号个数+1
            } 
            if (i - left < left) {//i - left为当前右括号个数,当前右括号个数小于左括号个数为合法
                p[i] = ')';
                dfs(i + 1, left);//左括号个数不变
            }
        };
        dfs(0, 0);
        return ans;
    }
};

39.组合总和

法一、从选和不选的视角

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int, int)> dfs = [&](int i, int t) {
            if (t == 0) {
                ans.push_back(p);
                return;
            }
            if (i == candidates.size() || t < 0) return;
            dfs(i + 1, t);//不选;
            p.push_back(candidates[i]);
            dfs(i, t - candidates[i]);//选;i表示当前元素还可以再次选中
            p.pop_back();
        };
        dfs(0, target);
        return ans;
    }
};

注意
可以将数组先排序然后剪枝,如果递归中发现 \(\rm left<candidates[i]\),由于后面的数字只会更大,所以无法把 \(\rm left\) 减小到 \(0\),可以直接返回。

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int, int)> dfs = [&](int i, int t) {
            if (t == 0) {
                ans.push_back(p);
                return;
            }
            if (i == candidates.size() || t < candidates[i]) return;
            dfs(i + 1, t);//不选;
            p.push_back(candidates[i]);
            dfs(i, t - candidates[i]);//选;i表示当前元素还可以再次选中
            p.pop_back();
        };
        dfs(0, target);
        return ans;
    }
};

法二、从枚举答案选哪个的视角

class Solution {
public:
    vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
        sort(candidates.begin(), candidates.end());
        vector<vector<int>> ans;
        vector<int> p;
        function<void(int, int)> dfs = [&](int i, int t) {
            if (t == 0) {
                ans.push_back(p);
                return;
            }
            if (i == candidates.size() || t < candidates[i]) return;
            for (int j = i; j < candidates.size(); j++) {
                p.push_back(candidates[j]);
                dfs(j, t - candidates[j]);
                p.pop_back();
            }
        };
        dfs(0, target);
        return ans;
    }
};

三、排列型回溯

46.全排列

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> ans;
        int n = nums.size();
        vector<int> p(n), st(n);
        function<void(int)> dfs = [&](int i) {//当前枚举到 p 的第几位
            if (i == n) {
                ans.push_back(p);
                return;
            }
            for (int j = 0; j < n; j++) {//枚举原数组哪一位使用了
                if (!st[j]) {
                    p[i] = nums[j];
                    st[j] = true;//j 位的数字被使用了
                    dfs(i + 1);
                    st[j] = false;//恢复现场
                }
            }
        };
        dfs(0);
        return ans;
    }
};
posted @ 2024-07-25 23:34  胖柚の工作室  阅读(13)  评论(0)    收藏  举报