1--回溯算法理论基础
回溯算法本质上是一个暴力搜索的过程,其常用于解决组合、切割、子集、排列等问题,其一般模板如下:
void backTracking(参数){
if(终止条件){
// 1. 收获结果;
// 2. return;
}
for(..遍历){
// 1. 处理节点
// 2. 递归搜索
// 3. 回溯 // 即撤销对节点的处理
}
return;
}
2--组合问题
主要思路:
基于回溯法,暴力枚举 k 个数,需注意回溯弹出元素的操作;
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combine(int n, int k) {
std::vector<int> path;
backTracking(n, k, path, 1); // 从第 1 个数开始
return res;
}
void backTracking(int n, int k, std::vector<int> path, int start){
if(path.size() == k){
res.push_back(path);
return;
}
for(int i = start; i <= n; i++){
path.push_back(i);
backTracking(n, k, path, i + 1); // 递归暴力搜索下一个数
path.pop_back(); // 回溯
}
}
private:
std::vector<std::vector<int>> res;
};
int main(int argc, char* argv[]){
int n = 4, k = 2;
Solution S1;
std::vector<std::vector<int>> res = S1.combine(n, k);
for(auto v : res){
for(int item : v) std::cout << item << " ";
std::cout << std::endl;
}
return 0;
}
3--组合问题的剪枝操作
上题的组合问题中,对于进入循环体 for(int i = start; i <= n; i++):
已选择的元素数量为:path.size()
仍然所需的元素数量为:k - path.size()
剩余的元素集合为:n - i + 1
则为了满足要求,必须满足:k-path.size() <= n - i + 1,即 i <= n - k + path.size() + 1
因此,可以通过以下条件完成剪枝操作:
for(int i = start; i <= i <= n - k + path.size() + 1; i++)
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combine(int n, int k) {
std::vector<int> path;
backTracking(n, k, path, 1); // 从第 1 个数开始
return res;
}
void backTracking(int n, int k, std::vector<int> path, int start){
if(path.size() == k){
res.push_back(path);
return;
}
for(int i = start; i <= n - k + path.size() + 1; i++){
path.push_back(i);
backTracking(n, k, path, i + 1); // 暴力下一个数
path.pop_back(); // 回溯
}
}
private:
std::vector<std::vector<int>> res;
};
int main(int argc, char* argv[]){
int n = 4, k = 2;
Solution S1;
std::vector<std::vector<int>> res = S1.combine(n, k);
for(auto v : res){
for(int item : v) std::cout << item << " ";
std::cout << std::endl;
}
return 0;
}
4--组合总和III
主要思路:
类似于上面的组合问题,基于回溯来暴力枚举每一个数,需要注意剪枝操作;
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combinationSum3(int k, int n) {
std::vector<int> path;
backTracking(k, n, 0, path, 1);
return res;
}
void backTracking(int k, int n, int sum, std::vector<int>path, int start){
if(sum > n) return; // 剪枝
if(path.size() == k){ // 递归终止
if(sum == n){
res.push_back(path);
}
return;
}
for(int i = start; i <= 9 + path.size() - k + 1; i++){ // 剪枝
path.push_back(i);
sum += i;
backTracking(k, n, sum, path, i + 1); // 递归枚举下一个数
// 回溯
sum -= i;
path.pop_back();
}
}
private:
std::vector<std::vector<int>> res;
};
int main(int argc, char* argv[]){
int k = 3, n = 7;
Solution S1;
std::vector<std::vector<int>> res = S1.combinationSum3(k, n);
for(auto v : res){
for(int item : v) std::cout << item << " ";
std::cout << std::endl;
}
return 0;
}
5--电话号码的字母组合
主要思路: