回溯算法
回溯算法是一种通过穷举来解决问题的方法,它的核心是从一个初始状态出发,暴力搜索所有可能的解决方案,当遇到正确的解则将其记录,直到找到解或者尝试了所有可能的选择都无法找到解为止。
- 回溯中回退
- 回溯中的减枝
- 常见代码框架
回溯中回退
给定一棵二叉树,搜索并记录所有值为 的节点,请返回节点列表。
cpp
void preOrder(TreeNode *root) {
if(root == nullptr) {
return;
}
// 尝试
path.push_back(root);
if(root ->val == 7) {
//记录解
res.push_back(path);
}
preOrder(root -> left);
preOrder(root -> right);
// 回退
path.pop_back();
}
}
回溯中的减枝
在二叉树中搜索所有值为 7 的节点,请返回根节点到这些节点的路径,并要求路径中不包含值为 3 的节点。
cpp
void preOrder(TreeNode *root) {
// 减枝
if(root == nullptr || root->val == 3) {
return;
}
// 尝试
path.push_back(root);
if(root->val == 7) {
res.push_back(path);
}
preOrder(root -> left);
preOrder(root -> right);
// 回退
path.pop_back();
}
常见代码框架
46.全排列
给定一个不含重复数字的数组nums,返回其所有可能的全排列,你可以按任意顺序返回答案。
思路分析
根据数据排列的特点,考虑深度优先搜索所有排列方案,就是通过元素交换,现固定第一位元素(n种情况)、再固定第2位元素(n - 1)种情况,最后固定第n位元素。
cpp
class Solution {
public:
vector<vector<int>> permute(vector<int>& nums) {
// i
dfs(nums, 0);
return res;
}
private:
vector<vector<int>> res; // 使用一个新的容器
void dfs(vector<int> nums, int x) {
if(x == nums.size() - 1) {
res.push_back(nums); //添加排列方案
return;
}
for(int i = x; i < nums.size();i++) {
swap(nums[i], nums[x]); // 使用交换固定在某个位置是我没有想到的
dfs(nums, x+1);
swap(nums[i], nums[x]); // 恢复交换
}
}
};
看完解析后的问题
- 是如何确定使用深度优先搜索的 --根据数组排列的特点...
- 如何想清楚终止条件
78.子集
给你一个重复的数组nums,数组中的元素互不相同。返回改数组所有可能的子集,解集不能保安重复的子集。你可以按任意顺序返回解集。
思路分析
对于选择输入的nums,考虑到每个nums[i]是选还是不选,由此组合出2的n次方个不同的子集。dfs中的i绊脚石当前考虑到nums[i]选或者不选。
cpp
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int n = nums.size();
vector<vector<int>> ans;
vector<int> pathl // 记录当前正在构造的子集
// 选或者是不选
auto dfs = [&](this auto&& dfs, int i) -> void {
if(i == n) {
ans.push_back(path); // 把当前子集加入答案
return;
}
// 不选nums[i].直接递归下一个
path.push_back(nums[i]);
dfs(i+ 1);
path.pop_back(); // 回溯撤销选择
};
dfs(0);
return ans;
}
}
ans.push_back(path)
-
将当前构造好的子集
path添加到答案集合。 -
做值拷贝,把当前 path 复制一份存入 ans。
path.push_back(nums[i]) -
把第
i个数加入当前子集,代表选择这个数。path.pop_back() -
回溯操作:撤销上一步的选择,恢复 path 状态。
-
保证递归返回后,上层分支不受下层修改影响。
17.电话号码的字母组合
给定一个包含2-9的字符串,返回所有可能的字母组合。可以按任意顺序返回
数字到字母的映射如下,与电话按键相同,注意1不对应任何字母。
思路分析
题目是需要去求可能的字母组合?
给定一个4如何使得程序返回 g h i?
题解
cpp
class Solution {
private:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
public:
vector<string> result; // 需要返回的结果一个容器
string s; // 来收集叶子节点
void backtracking(const string& digits, int index) { // 记录遍历第几个数字了。
if(index == digits.size()) {
result.push_back(s);
return;
}
int digit = digits[index] - '0';//将index指向的数字转为int
string letters = letterMap[digit];// 取数字对应的字符集
for(int i = 0; i < letters.size();i++) {
s.push_back(letters[i]); // 处理
backtracking(digits, index + 1); // 递归处理
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
s.clear();
result.clear();
if(digits.size() == 0) {
return result;
}
backtracking(digits, 0);
return result;
}
};
看完题解后的问题
-
两个字母就是两个for循环,三个字符就是三个for循环,然后类推吗
-
数字和字母如何去映射呢
-
处理 1 和 * #的异常情况单层遍历的逻辑是什么样的
-
单层遍历的逻辑是什么样的
cpp
int digit = digits[index] - '0'; // index指向的数字转换为int
string letters = letterMap[digit] 取数字对应的字符集
for(int i = 0; i < letters.size(); i++) {
s.push_back(letters[i]);// 处理
backtracking(digits, index + 1);
s.pop_back();
}
- 终止条件是怎么样的. -- 当index等于输入的数字的时候。
cpp
if (index == digits.size()) {
result.push_back(s);
return;
}