一. 回溯法
回溯也叫回溯搜索法,它是搜索的一种方式,用来解决可以抽象为树形结构的问题。回溯是递归的副产品,只要由递归就会有回溯。
回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案。
回溯法并不高效,但有些问题只能通过回溯暴力搜索求解:
- 组合问题:N 个数里按一定规则找出 K 个数的集合,不强调元素顺序
- 子集问题:一个 N 个数的集合有多少符合条件的子集
- 排列问题:N 个数按一定规则全排列,有几种排列方式,强调元素顺序
- 棋盘问题:N 皇后,解数独等
1.1 组合问题
给定两个整数 n 和 k,返回 1…n 中所有可能的 k 个数的集合
template<typename T>
std::ostream &operator<<(std::ostream &os, std::vector<std::vector<T>> vector){
os << '{';
for(int i = 0; i < vector.size(); ++i){
os << '{';
for(int j = 0; j < vector[i].size(); ++j){
os << vector[i][j];
if(j != vector[i].size() - 1)
os << ", ";
}
os << "}";
if(i != vector.size() - 1)
os << ", ";
}
os << "}";
return os;
}
class Solution{
public:
std::vector<std::vector<int>> combine(int n, int k){
std::vector<std::vector<int>> result;
std::vector<int> path;
auto inner = [&result, &path, n, k](const auto &inner, int start) -> void{
if(path.size() == k){
result.emplace_back(path);
return;
}
for(int i = start; i <= n - (k - path.size()) + 1; ++i){
path.emplace_back(i);
inner(inner, i + 1);
path.pop_back();
}
};
inner(inner, 1);
return result;
}
};
剪枝优化
1.2 子集问题
组合问题可以认为是找出树形结构的叶子节点,子集问题可以认为是找出树形结构的所有节点
class Solution{
public:
std::vector<std::vector<int>> subsets(const std::vector<int> &nums){
std::vector<std::vector<int>> result;
std::vector<int> path;
auto inner = [&result, &path, &nums](const auto &inner, int start) -> void{
result.emplace_back(path);
for(int i = start; i < nums.size(); ++i){
path.emplace_back(nums[i]);
inner(inner, i + 1);
path.pop_back();
}
};
inner(inner, 0);
return result;
}
};
1.3 排列问题
给定一个没有重复数字的序列,返回其所有可能的全排列
排列是有序的,即 {1, 2} 和 {2, 1} 是两个集合,这与组合问题,子集问题是不同的
需要使用 used 数组记录此时 path 里都有哪些元素使用了,一个排列里元素只能使用一次
class Solution{
public:
std::vector<std::vector<int>> permutation(const std::vector<int> &nums){
std::vector<std::vector<int>> result;
std::vector<int> path;
std::vector<bool> used(nums.size(), false);
auto inner = [&result, &path, &nums, &used](const auto &inner) -> void{
if(path.size() == nums.size()){
result.emplace_back(path);
return;
}
for(int i = 0; i < nums.size(); ++i){
if(used[i])
continue;
used[i] = true;
path.emplace_back(nums[i]);
inner(inner);
path.pop_back();
used[i] = false;
}
};
inner(inner);
return result;
}
};
1.4 N 皇后问题
N 皇后问题研究的是如何将 N 个皇后放置在 nxn 的棋盘上,并且使皇后彼此之间不能相互攻击
std::ostream &operator<<(std::ostream &os, std::vector<std::vector<std::string>> vector){
for(int i = 0; i < vector.size(); ++i){
for(int j = 0; j < vector[i].size(); ++j){
os << vector[i][j];
if(j != vector[i].size() - 1)
os << '\n';
}
if(i != vector.size() - 1)
os << "\n==================================\n";
}
return os;
}
class Solution{
public:
std::vector<std::vector<std::string>> NQueens(int N){
auto is_valid = [N](std::vector<std::string> chess_board, int row, int col){
for(int i = 0; i < row; ++i)
if(chess_board[i][col] == 'Q')
return false;
for(int i = row - 1, j = col - 1; i >= 0 && j >= 0; --i, --j)
if(chess_board[i][j] == 'Q')
return false;
for(int i = row - 1, j = col + 1; i >= 0 && j < N; --i, ++j)
if(chess_board[i][j] == 'Q')
return false;
return true;
};
std::vector<std::vector<std::string>> result;
std::vector<std::string> chess_board(N, std::string(N, '.'));
auto inner = [&result, &chess_board, &is_valid, N](const auto &inner, int row) -> void{
if(row == N){
result.emplace_back(chess_board);
return;
}
for(int col = 0; col < N; ++col){
if(is_valid(chess_board, row, col)){
chess_board[row][col] = 'Q';
inner(inner, row + 1);
chess_board[row][col] = '.';
}
}
};
inner(inner, 0);
return result;
}
};