1--回溯算法理论基础
回溯算法本质上是一个暴力搜索 的过程,其常用于解决组合 、切割 、子集 、排列 等问题,其一般模板如下:
cpp
void backTracking(参数){
if(终止条件){
// 1. 收获结果;
// 2. return;
}
for(..遍历){
// 1. 处理节点
// 2. 递归搜索
// 3. 回溯 // 即撤销对节点的处理
}
return;
}
2--组合问题
主要思路:
基于回溯法,暴力枚举 k 个数,需注意回溯弹出元素的操作;
cpp
#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++)
cpp
#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
主要思路:
类似于上面的组合问题,基于回溯来暴力枚举每一个数,需要注意剪枝操作;
cpp
#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--电话号码的字母组合
主要思路:
根据传入的字符串,遍历每一个数字字符,基于回溯法,递归遍历每一个数字字符对应的字母;
cpp
#include <iostream>
#include <vector>
#include <string>
class Solution {
public:
std::vector<std::string> letterCombinations(std::string digits) {
if(digits.length() == 0) return res;
std::vector<std::string> letter = {"", " ", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
backTracking(digits, letter, 0);
return res;
}
void backTracking(std::string digits, std::vector<std::string> letter, int cur){
if(cur == digits.size()){
res.push_back(tmp);
return;
}
int letter_idx = digits[cur] - '0';
int letter_size = letter[letter_idx].size();
for(int i = 0; i < letter_size; i++){
tmp.push_back(letter[letter_idx][i]);
backTracking(digits, letter, cur+1);
// 回溯
tmp.pop_back();
}
}
private:
std::vector<std::string> res;
std::string tmp;
};
int main(int argc, char* argv[]){
std::string test = "23";
Solution S1;
std::vector<std::string> res = S1.letterCombinations(test);
for(std::string str : res) std::cout << str << " ";
std::cout << std::endl;
return 0;
}
6--组合总和
主要思路:
经典组合问题,通过回溯法暴力递归遍历;
cpp
#include <iostream>
#include <vector>
class Solution {
public:
std::vector<std::vector<int>> combinationSum(std::vector<int>& candidates, int target) {
if(candidates.size() == 0) return res;
backTracking(candidates, target, 0, 0, path);
return res;
}
void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<int>path){
if(sum > target) return; // 剪枝
if(sum == target) {
res.push_back(path);
return;
}
for(int i = start; i < candidates.size(); i++){
sum += candidates[i];
path.push_back(candidates[i]);
backTracking(candidates, target, sum, i, path);
// 回溯
sum -= candidates[i];
path.pop_back();
}
}
private:
std::vector<std::vector<int>> res;
std::vector<int> path;
};
int main(int argc, char* argv[]){
// candidates = [2,3,6,7], target = 7
std::vector<int> test = {2, 3, 6, 7};
int target = 7;
Solution S1;
std::vector<std::vector<int>> res = S1.combinationSum(test, target);
for(auto vec : res){
for(int val : vec) std::cout << val << " ";
std::cout << std::endl;
}
return 0;
}
7--组合总和II
主要思路:
基于回溯法,暴力递归遍历数组;
本题不能包含重复的组合,但由于有重复的数字,因此需要进行树层上的去重;
cpp
#include <iostream>
#include <vector>
#include <algorithm>
class Solution {
public:
std::vector<std::vector<int>> combinationSum2(std::vector<int>& candidates, int target) {
if(candidates.size() == 0) return res;
std::sort(candidates.begin(), candidates.end());
std::vector<bool> visited(candidates.size(), false);
backTracking(candidates, target, 0, 0, visited);
return res;
}
void backTracking(std::vector<int>& candidates, int target, int sum, int start, std::vector<bool>& visited){
if(sum > target) return; // 剪枝
if(sum == target){
res.push_back(path);
return;
}
for(int i = start; i < candidates.size(); i++){
// 树层剪枝去重
if(i > 0 && candidates[i-1] == candidates[i]){
if(visited[i-1] == false) continue;
}
sum += candidates[i];
path.push_back(candidates[i]);
visited[i] = true;
backTracking(candidates, target, sum, i+1, visited);
// 回溯
sum -= candidates[i];
path.pop_back();
visited[i] = false;
}
}
private:
std::vector<std::vector<int>> res;
std::vector<int> path;
};
int main(int argc, char* argv[]){
// candidates = [10,1,2,7,6,1,5], target = 8
std::vector<int> test = {10, 1, 2, 7, 6, 1, 5};
int target = 8;
Solution S1;
std::vector<std::vector<int>> res = S1.combinationSum2(test, target);
for(auto vec : res){
for(int val : vec) std::cout << val << " ";
std::cout << std::endl;
}
return 0;
}