梦想不会自己发光,真正闪耀的是那个为梦狂奔的你。献给知行的孩子们!(Eric.He著)
回溯算法的本质:它是一种深度优先搜索(DFS)的暴力枚举策略,核心是“尝试 - 回退”—— 在解决问题的过程中,一步步尝试所有可能的选择,当发现当前选择无法得到合法解时,就回退到上一步,撤销当前选择,继续尝试其他可能,直到找到所有解 / 一个合法解。
问题描述:
算法解析:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void permute2(vector<int>& nums, vector<bool>& used, vector<int>& path, vector<vector<int>>& result) {
// 终止条件:路径长度等于数组长度,找到一个完整排列
if (path.size() == nums.size()) {
result.push_back(path);
return;
}
for (int i = 0; i < nums.size(); ++i) {
// 跳过已使用的元素
if (used[i]) {
continue;
}
// 去重核心逻辑:同一层递归中,跳过重复元素
// i > 0 避免越界,nums[i] == nums[i-1] 是重复元素
// !used[i-1] 说明前一个相同元素未被使用(同一层),跳过当前元素
if (i > 0 && nums[i] == nums[i-1] && !used[i-1]) {
continue;
}
// 选择当前元素
used[i] = true;
path.push_back(nums[i]);
// 递归深入下一层
permute2(nums, used, path, result);
// 回溯:撤销选择
path.pop_back();
used[i] = false;
}
}
int main() {
vector<vector<int>> result; // 存储最终结果
vector<int> path; // 存储当前路径
vector<bool> used; // 标记元素是否被使用,初始化为 false
vector<int> nums = {1,2,3};
used.resize(nums.size(), false); // 初始化标记数组,全部为未使用
permute2(nums, used, path, result);
for(vector<int> item : result)
{
cout << "[";
for(int node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ 1 2 3 ]
[ 1 3 2 ]
[ 2 1 3 ]
[ 2 3 1 ]
[ 3 1 2 ]
[ 3 2 1 ]
问题描述:
算法解析:
这道题的关键是去重(数组有重复元素,但组合不能重复)+ 回溯剪枝(元素仅用一次),核心思路:
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
vector<vector<int>> res_sum; // 存储所有合法组合
vector<int> path_sum; // 存储当前组合路径
// 回溯函数:
// candidates: 排序后的数组
// target: 剩余目标值
// start: 当前遍历起始索引(元素仅用一次,递归传i+1)
void combinationSum2(vector<int>& candidates, int target, int start) {
// 终止条件1:找到合法组合
if (target == 0) {
res_sum.push_back(path_sum);
return;
}
// 终止条件2:剩余目标值 < 0,剪枝
if (target < 0) {
return;
}
// 遍历当前层的元素(从start开始,避免重复使用元素)
for (int i = start; i < candidates.size(); ++i) {
// 核心去重:同一层递归中,跳过重复元素(排序后相邻重复)
// i > start 保证是同一层的重复,而非同一树枝的重复
if (i > start && candidates[i] == candidates[i-1]) {
continue;
}
// 选择当前元素
path_sum.push_back(candidates[i]);
// 递归:元素仅用一次,起始索引传i+1,剩余目标值减当前元素
combinationSum2(candidates, target - candidates[i], i + 1);
// 回溯:撤销选择
path_sum.pop_back();
}
}
int main() {
vector<int> candidates = {10,1,2,7,6,1,5};
int target = 8;
// 关键:排序,让重复元素相邻,为去重做准备
sort(candidates.begin(), candidates.end());
combinationSum2(candidates, target, 0);
for(vector<int> item : res_sum)
{
cout << "[";
for(int node : item )
{
cout << " " << node << " ";
}
cout << "]" << endl;
}
return 0;
}
[ 1 1 6 ]
[ 1 2 5 ]
[ 1 7 ]
[ 2 6 ]
问题描述:
算法解析:
区间选点问题的最优贪心策略是按区间右端点升序排序,核心逻辑为:每次选择当前未覆盖区间的右端点作为站点,该策略能让单个站点覆盖尽可能多的后续区间,从而保证站点数量最少。
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void letterCombinations(const string& digits, const string mapping[], int index,
string& path, vector<string>& result) {
// 终止条件:处理完所有数字,找到一个完整组合
if (index == digits.size()) {
result.push_back(path);
return;
}
// 获取当前数字对应的字母串(digits[index] 是字符,减 '0' 转成数字)
int digit = digits[index] - '0';
const string& letters = mapping[digit];
// 遍历当前数字对应的所有字母
for (char c : letters) {
// 选择当前字母
path.push_back(c);
// 递归处理下一个数字
letterCombinations(digits, mapping, index + 1, path, result);
// 回溯:撤销选择
path.pop_back();
}
}
int main() {
vector<string> result; // 存储最终结果
string path; // 当前拼接的字符串(路径)
string digits = "23";
// 数字到字母的映射表(索引对应数字,0/1 无意义)
const string mapping[] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz" // 9
};
letterCombinations(digits, mapping, 0, path, result);
for(string item : result)
{
cout << "[";
cout << " " << item << " ";
cout << "]" << endl;
}
return 0;
}
[ ad ]
[ ae ]
[ af ]
[ bd ]
[ be ]
[ bf ]
[ cd ]
[ ce ]
[ cf ]