前言:
回溯算法是一种经典的算法思想,通常用于解决组合问题、排列问题、子集问题等。它的核心思想是通过递归和回溯的方式穷举所有可能的解,并在过程中剪枝以减少不必要的计算。
回溯算法的核心思想
-
穷举所有可能:通过递归的方式遍历所有可能的解。
-
剪枝:在递归过程中,如果发现当前路径不可能得到解,则立即停止继续探索,回溯到上一步。
-
路径维护:在递归过程中维护当前路径的状态,并在递归结束后恢复状态(回溯)。
回溯算法的通用框架
回溯算法通常遵循以下步骤:
-
定义递归函数:通常是一个函数,用于处理当前的状态。
-
终止条件:当满足某个条件时,停止递归。
-
选择与回溯:遍历所有可能的选择,做出选择后递归调用,递归返回后撤销选择。
void backtrack(路径, 选择列表) {
if (满足终止条件) {
result.add(路径);
return;
}
for (选择 in 选择列表) {
做选择;
backtrack(路径, 新的选择列表);
撤销选择;
}
}
LeetCode例题:
全排列46. 全排列
https://leetcode.cn/problems/permutations/
题解:
class solution(){
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
backtrace(res, nums, 0, nums.size());
return res;
}
void backtrace(vector<vector<int>>& res, vector<int> output, int first, int len){
if (first == len){ // 满足条件
res.emplace_back(output);
return;
}
for (int i = first; i < len; i++){
swap(output[i], output[first]); // 做选择
backtrace(res, output, first + 1, len);
swap(output[i], output[first]); // 撤销选择
}
}
};
子集
78. 子集https://leetcode.cn/problems/subsets/
题解:
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> path;
backtrace(nums, path, 0);
return res;
}
void backtrace(vector<int>& nums, vector<int> path, int start){
res.emplace_back(path);
for (int i = start; i < nums.size(); i++){
path.emplace_back(nums[i]);
backtrace(nums, path, i + 1);
path.pop_back();
}
}
};
电话号码的字母组合
题解:
class Solution {
public:
unordered_map<char, string> mp{
{'2', "abc"},
{'3', "def"},
{'4', "ghi"},
{'5', "jkl"},
{'6', "mno"},
{'7', "pqrs"},
{'8', "tuv"},
{'9', "wxyz"}
};
vector<string> letterCombinations(string digits) {
vector<string> res;
if (digits.empty()) return res;
string str;
backtrace(res, mp, digits, 0, str);
return res;
}
void backtrace(vector<string>& res, const unordered_map<char, string>& mp, const string& digits, int index, string& str){
if (index == digits.size()){
res.emplace_back(str);
return;
}
char digit = digits[index];
const string& letters = mp.at(digit);
for (const char& c : letters){
str.push_back(c);
backtrace(res, mp, digits, index + 1, str);
str.pop_back();
}
}
};
组合总和
39. 组合总和https://leetcode.cn/problems/combination-sum/
题解:
class Solution {
public:
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> res;
sort(candidates.begin(), candidates.end());
vector<int> path;
backtrace(res, candidates, path, 0, target);
return res;
}
void backtrace(vector<vector<int>>& res,vector<int>& candidates,vector<int>& path, int index, int target){
if (target < 0) return;
if (target == 0){
res.emplace_back(path);
return;
}
for (int i = index; i < candidates.size() && target - candidates[i] >= 0; i++){
path.emplace_back(candidates[i]);
backtrace(res, candidates, path, i, target - candidates[i]);
path.pop_back();
}
}
};
括号生成
22. 括号生成https://leetcode.cn/problems/generate-parentheses/
class Solution {
public:
vector<string> generateParenthesis(int n) {
vector<string> res;
string path;
backtrace(res, path, 0, 0, n);
return res;
}
void backtrace(vector<string>& res, string& path, int open, int close, int n){
if (path.size() == n * 2){
res.emplace_back(path);
return;
}
if (open < n){
path.push_back('(');
backtrace(res, path, open + 1, close, n);
path.pop_back();
}
if (close < open){
path.push_back(')');
backtrace(res, path, open, close + 1, n);
path.pop_back();
}
}
};