文章目录
- 算法介绍
- 回溯算法能解决的问题
- 解题模板
- 1. 组合问题
- 2. N皇后问题
算法介绍
回溯法(Back Tracking Method
)(探索与回溯法)是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为 “回溯点”。
回溯算法实际上一个类似枚举的搜索尝试过程,主要是在搜索尝试过程中寻找问题的解,当发现已不满足求解条件时,就 “回溯” 返回,尝试别的路径。
可以把回溯法看成是递归调用的一种特殊形式。
回溯法思路的简单描述是:把问题的解空间转化成了图或者树的结构表示,然后使用深度优先搜索策略进行遍历,遍历的过程中记录和寻找所有可行解或者最优解。
回溯算法能解决的问题
- 组合问题:N个数里面按一定规则找出k个数的集合
- 排列问题:N个数按一定规则全排列,有几种排列方式
- 切割问题:一个字符串按一定规则有几种切割方式
- 子集问题:一个N个数的集合里有多少符合条件的子集
- 棋盘问题:N皇后,解数独等等
解题模板
代码方面,回溯算法的框架:
result = []
def backtracking(路径, 选择列表):
// 确定终止条件
if 满足结束条件:
result.add(路径)
return
// 单层搜索
for 选择 in 选择列表:
做选择
backtracking(路径, 选择列表)
撤销选择
核心就是 for 循环里面的递归,在递归调用之前「做选择」,在递归调用之后「撤销选择」。
总结就是:
循环 + 递归 = 回溯
1. 组合问题
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startIndex, vector<bool>& used) {
if (sum == target) {
result.push_back(path);
return;
}
for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
// used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
// used[i - 1] == false,说明同一树层candidates[i - 1]使用过
// 要对同一树层使用过的元素进行跳过
if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
used[i] = true;
backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1,这里是i+1,每个数字在每个组合中只能使用一次
used[i] = false;
sum -= candidates[i];
path.pop_back();
}
}
public:
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
vector<bool> used(candidates.size(), false);
path.clear();
result.clear();
// 首先把给candidates排序,让其相同的元素都挨在一起。
sort(candidates.begin(), candidates.end());
backtracking(candidates, target, 0, 0, used);
return result;
}
};
这个组合问题中,最主要的问题是搞清楚 树层去重
和 树枝去重
2. N皇后问题
class Solution {
public:
// 存放不同解法
vector<vector<string>> result;
void backtracking(int n, int row, vector<string> chessboard) {
if(row == n) {
result.push_back(chessboard);
return;
}
for(int colu = 0; colu < n; colu++) {
if(IsValid(n, row, colu, chessboard)) { // 验证合法就可以放
chessboard[row][colu] = 'Q'; // 放置皇后
backtracking(n, row + 1, chessboard);
chessboard[row][colu] = '.'; // 回溯,撤销皇后
}
}
}
bool IsValid(int n, int row, int colu, vector<string> chessboard) {
// 检查放到此处是否有效, 同列、对角是否有其他皇后(回溯时每行只取一次,所以不用检查同行)
// 向上检查同列是否有皇后
for(int i = 0; i < row; i++) {
if(chessboard[i][colu] == 'Q') {
return false;
}
}
// 检查 135°(左上)是否有皇后
for(int i = row-1, j = colu - 1; i >= 0 && j >= 0; i--, j--) {
if(chessboard[i][j] == 'Q') {
return false;
}
}
// 检查 45°(右上)是否有皇后
for(int i = row-1, j = colu + 1; i >= 0 && j < n; i--, j++) {
if(chessboard[i][j] == 'Q') {
return false;
}
}
return true;
}
vector<vector<string>> solveNQueens(int n) {
// 棋盘初始化
vector<string> chessboard(n, string(n, '.'));
// n:棋盘大小 0:从棋盘0行开始选
backtracking(n, 0, chessboard);
return result;
}
};