回溯算法(C/C++)

embedded/2025/3/4 5:21:46/

目录

 一、组合问题

组合

组合剪枝

组合总和 III​编辑

组合总和​编辑

组合总和 II

电话号码的字母组合​编辑

二、分割问题

分割回文串

复原 IP 地址

三、集合问题

子集

子集 II

 非递减子序列

四、排列问题

全排列

全排列 II

五、棋盘问题

 N 皇后


课程:

【带你学透回溯算法(理论篇)| 回溯法精讲!】https://www.bilibili.com/video/BV1cy4y167mM?vd_source=342079de7c07f82982956aad8662b467

关于回溯算法,你该了解这些!

  • 组合问题:N个数里面按一定规则找出k个数的集合
  • 排列问题:N个数按一定规则全排列,有几种排列方式
  • 切割问题:一个字符串按一定规则有几种切割方式
  • 子集问题:一个N个数的集合里有多少符合条件的子集
  • 棋盘问题:N皇后,解数独等等
void backtracking(参数) {if (终止条件) {存放结果;return;}for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {处理节点;backtracking(路径,选择列表); // 递归回溯,撤销处理结果}
}

 一、组合问题

组合

class Solution {
public:void backtracking(int pos, vector<int> &path, vector<vector<int>>& res, int k, int n) {if (path.size()==k) {res.push_back(path);return;}for (int i = pos;i < n+1;i++) {path.push_back(i);backtracking(i + 1, path, res, k, n);path.pop_back();}}vector<vector<int>> combine(int n, int k) {vector<vector<int>> res;vector<int> path;backtracking(1, path, res, k, n);return res;}
};

组合剪枝

 在上面的这个组合的代码,实际上我们可以进一步去优化,也就是对构造的树进行剪枝的操作。我们可以这么理解,已知可以选择走的路径总长度为n,在当前的横向遍历节点上,已知前面走过路径上长度为size,然后总共是需要走的长度为k,那么是不是可以说还需要走k-size的长度的路径?是吧,那也意味着在当前横向遍历的节点长度最起码是从n-(k-size)+1开始,这个加一是表示字节当前这个节点。

修改的代码如下: 

class Solution {
public:void backtracking(int pos, vector<int> &path, vector<vector<int>>& res, int k, int n) {if (path.size()==k) {res.push_back(path);return;}for (int i = pos;i <= n - (k - path.size()) + 1;i++) {path.push_back(i);backtracking(i + 1, path, res, k, n);path.pop_back();}}vector<vector<int>> combine(int n, int k) {vector<vector<int>> res;vector<int> path;backtracking(1, path, res, k, n);return res;}
};

下面三个案例练练手看看。

组合总和 III
class Solution {
public:void backtracking(int pos,int &sum,int n, int k,vector<int> &path ,vector<vector<int>> &re){if (sum == n && path.size() == k) {re.push_back(path);return;}if (path.size() == k) {return;}for (int i = pos;i <= 9;i++) {if (sum + i <= n) {sum += i;path.push_back(i);backtracking(i + 1, sum, n, k, path, re);sum -= i;path.pop_back();}}}vector<vector<int>> combinationSum3(int k, int n) {vector<vector<int>> re;vector<int> path;int sum = 0;backtracking(1, sum, n, k, path, re);return re;}
};

这里可以看到执行的剪枝操作就是,当sum+i大于目标值n的时候就不执行深度递归操作。

组合总和
class Solution {
public:void backtracking(int pos, int sum, int target, vector<vector<int>>& re, vector<int>& candidates,vector<int> path) {if (sum == target) {re.push_back(path);return;}if(pos==candidates.size() || sum>target){return ;}for (int i = pos;i < candidates.size() && sum + candidates[i] <= target;i++) {sum += candidates[i];path.push_back(candidates[i]);backtracking(i, sum, target, re, candidates, path);sum -= candidates[i];path.pop_back();}}vector<vector<int>> combinationSum(vector<int>& candidates, int target) {sort(candidates.begin(),candidates.end());vector<vector<int>> re;vector<int> path;backtracking(0, 0, target, re, candidates, path);return re;}
};
组合总和 II

class Solution {
public:void backtracking(int pos,int target, int sum,unordered_map<int, bool> used ,vector<int> path, vector<int> candidates,vector<vector<int>> &re){if (sum == target) {re.push_back(path);return;}for (int i = pos;i < candidates.size() && sum + candidates[i] <= target;i++) {if(i>0 && candidates[i]==candidates[i-1] && used[candidates[i-1]]==false){continue;}sum += candidates[i];path.push_back(candidates[i]);used[candidates[i]] = true;backtracking(i + 1, target, sum,used, path, candidates, re);sum -= candidates[i];path.pop_back();used[candidates[i]] = false;}}vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {sort(candidates.begin(), candidates.end());vector<vector<int>> re;vector<int> path;unordered_map<int, bool> used;backtracking(0, target, 0, used, path, candidates, re);return re;}
};
电话号码的字母组合
class Solution {
public:void backtracking(int pos,string path,string digits, vector<string> map, vector<string>& res){if (path.size() == digits.size() ) {res.push_back(path);return;}int num = digits[pos] - '0';for (int i = 0;i < map[num].size();i++) {path.push_back(map[num][i]);backtracking(pos + 1, path, digits, map, res);path.pop_back();}}vector<string> letterCombinations(string digits) {vector<string> map = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};vector<string> res;string path = "";if(digits.size() >0)backtracking(0, path, digits, map, res);return res;}
};

二、分割问题

分割回文串

这个切割问题其实类似上面那些组合问题,其实就是按照字符串的顺序去进行划分子串组合,然后限制条件就是回文串,如果是回文串就添加到路径中,如果不是回文串就跳过。 

class Solution {
public:bool is_huiwen(string s) {int i = 0, j = s.size()-1;while (i < j) {if (s[i] != s[j]) {return false;}i++;j--;}return true;}void backtracking(int pos, vector<string> route, string path, string s, vector<vector<string>> &res) {if (pos == s.size()) {res.push_back(route);route.clear();return;}for (int i = pos;i < s.size();i++) {string temp = s.substr(pos,i-pos+1);if (is_huiwen(temp)) {route.push_back(temp);backtracking(i + 1, route, path, s, res);route.pop_back();}}}vector<vector<string>> partition(string s) {vector<vector<string>> res;vector<string> route;string path;backtracking(0, route, path, s, res);return res;}
};

复原 IP 地址

class Solution {
public:bool isvalid(string s) {if(s.empty() || s.size()>3){return false;}if (s.size() >1 &&s[0] == '0') {return false;}int num = stoi(s);if (num > 255 || num<0) {return false;}return true;}void backtracking(int pos,int start, string tmp, string s, vector<string>& res) {if (pos == 3) {string t = s.substr(start, s.size() -pos+ 1);if (isvalid(t)) {tmp += t;res.push_back(tmp);}return;}for (int i = start;i < s.size();i++) {string t = s.substr(start,i-start+1);if (isvalid(t)) {for (int j = 0;j < t.size();j++) {tmp.push_back(t[j]);}tmp.push_back('.');pos++;backtracking(pos, i + 1, tmp, s, res);for (int j = 0;j < t.size();j++) {tmp.pop_back();}tmp.pop_back();pos--;}}}vector<string> restoreIpAddresses(string s) {vector<string> res;string tmp;backtracking(0,0, tmp, s, res);return res;}
};

三、集合问题

 对于子集问题,其实跟前面的不同,子集是每一个递归的节点都要进行数据的收集,而前面的组合问题都是有条件限制的才执行收集的操作,比如递归收集数字和为target的组合之类的。

子集

回溯算法:求子集问题!

class Solution {
public:void backtracking(int start_index, vector<int>& nums, vector<int>& path, vector<vector<int>>& res) {res.emplace_back(path);for (int i = start_index;i < nums.size();i++) {path.push_back(nums[i]);backtracking(i + 1, nums, path, res);path.pop_back();}}vector<vector<int>> subsets(vector<int>& nums) {vector<vector<int>> res;vector<int> path;backtracking(0, nums, path, res);return res;}
};

子集 II

这个问题其实是组合总和2的一个派生问题,本质上是差不多的,无法就是需要一个东西来去标记一下使用过的数据,避免下次重新去使用。

class Solution {
public:void backtracking(int pos, unordered_map<int, bool> &used, vector<int>& nums, vector<int>& path, vector<vector<int>>& res) {res.emplace_back(path);for (int i = pos; i < nums.size();i++) {if (i > 0 && nums[i] == nums[i - 1] && used[nums[i - 1]] == false) {continue;}path.push_back(nums[i]);used[nums[i]] = true;backtracking(i + 1, used, nums, path, res);used[nums[i]] = false;path.pop_back();}}vector<vector<int>> subsetsWithDup(vector<int>& nums) {sort(nums.begin(), nums.end());vector<vector<int>> res;vector<int> path;unordered_map<int, bool> used;backtracking(0, used, nums, path, res);return res;}
};

 非递减子序列

对于这个问题,也就是说每一层需要去避免重复使用同一个数字,也就是需要在每一个递归节点去创建一个作为标记的哈希表来依次标记已经遍历的节点,这里对比前面使用了纵向哈希表是不一样的,这个是横向的。

class Solution {
public:void backtracking(int pos,vector<int> &path, vector<int>& nums,vector<vector<int>> &res) {if(path.size()>=2)res.emplace_back(path);if (pos == nums.size()) {return;}unordered_map<int, bool> used;for (int i = pos;i < nums.size();i++) {if (path.size() > 0 && path.back() > nums[i]) {continue;}if (used[nums[i]]) {continue;}path.push_back(nums[i]);used[nums[i]] = true;backtracking(i + 1, path, nums,res);path.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {vector<vector<int>> res;vector<int> path;backtracking(0, path, nums, res);return res;}
};
class Solution {
public:void backtracking(int pos,vector<int> &path, vector<int>& nums,vector<vector<int>> &res) {if(path.size()>=2)res.emplace_back(path);if (pos == nums.size()) {return;}int used[201] = {0};for (int i = pos;i < nums.size();i++) {if (path.size() > 0 && path.back() > nums[i]) {continue;}if (used[nums[i]+100]) {continue;}path.push_back(nums[i]);used[nums[i]+100] = true;backtracking(i + 1, path, nums,res);path.pop_back();}}vector<vector<int>> findSubsequences(vector<int>& nums) {vector<vector<int>> res;vector<int> path;backtracking(0, path, nums, res);return res;}
};

四、排列问题

全排列

class Solution {
public:void backtracking(int pos,unordered_map<int, bool>& used, vector<int>& nums, vector<int>& path, vector<vector<int>>& res) {if (path.size() == nums.size()) {res.emplace_back(path);return;}for (int i = 0;i < nums.size();i++) {if (used[nums[i]] == false) {used[nums[i]] = true;path.push_back(nums[i]);backtracking(i + 1, used,nums, path, res);used[nums[i]] = false;path.pop_back();}}}vector<vector<int>> permute(vector<int>& nums) {vector<vector<int>> res;vector<int> path;unordered_map<int, bool> used;backtracking(0,used, nums, path, res);return res;}
};

全排列 II

class Solution {
public:void backtracking(int pos,unordered_map<int, bool> &map, vector<int> &nums, vector<int> &path, vector<vector<int>>& res) {if (path.size() == nums.size()){res.emplace_back(path);return;}bool used[21] = { false };for (int i = 0;i < nums.size();i++) {if (! used[nums[i]+10] && !map[i]) {used[nums[i] + 10] = true;map[i] = true;path.push_back(nums[i]);backtracking(pos + 1,map, nums, path, res);path.pop_back();map[i] = false;}}}vector<vector<int>> permuteUnique(vector<int>& nums) {unordered_map<int, bool> map;vector<vector<int>> res;vector<int> path;unordered_map<int, bool> used;backtracking(0,map, nums, path, res);return res;}
};
class Solution {
public:void backtracking(int pos,unordered_map<int, bool> &map, vector<int> &nums, vector<int> &path, vector<vector<int>>& res) {if (path.size() == nums.size()){res.emplace_back(path);return;}bool used[21] = { false };for (int i = 0;i < nums.size();i++) {if (! used[nums[i]+10] && !map[i]) {used[nums[i] + 10] = true;map[i] = true;path.push_back(nums[i]);backtracking(pos + 1,map, nums, path, res);path.pop_back();map[i] = false;}}}vector<vector<int>> permuteUnique(vector<int>& nums) {unordered_map<int, bool> map;vector<vector<int>> res;vector<int> path;unordered_map<int, bool> used;backtracking(0,map, nums, path, res);return res;}
};

五、棋盘问题

 N 皇后

class Solution {
public:bool isok(int x, int y, int n, vector<string>& path) {// 同一列不同行检查for (int i = 0;i < x;i++) {if (path[i][y] == 'Q') {return false;}}// 右上对角线检查for (int i = x, j = y;i >= 0 && j < n;i--, j++) {if (path[i][j] == 'Q') {return false;}}// 左上对角线检查for (int i = x, j = y;i >= 0 && j >= 0;i--, j--) {if (path[i][j] == 'Q') {return false;}}return true;}void backtracking(int pos,int n, vector<string>& path, vector<vector<string>>& res) {if (pos == n) {res.push_back(path);return;}for (int i = 0;i < n;i++) {if (isok( pos,  i, n, path)) {path[pos][i] = 'Q';backtracking(pos + 1, n, path, res);path[pos][i] = '.';}}}vector<vector<string>> solveNQueens(int n) {vector<vector<string>> res;vector<string> path(n, string(n, '.'));backtracking(0, n, path, res);return res;}
};


http://www.ppmy.cn/embedded/169818.html

相关文章

[Lc滑动窗口_1] 长度最小的数组 | 无重复字符的最长子串 | 最大连续1的个数 III | 将 x 减到 0 的最小操作数

目录 1. 长度最小的字数组 题解 代码 ⭕2.无重复字符的最长子串 题解 代码 3.最大连续1的个数 III 题解 代码 4.将 x 减到 0 的最小操作数 题解 代码 1. 长度最小的字数组 题目链接&#xff1a;209.长度最小的字数组 题目分析: 给定一个含有 n 个 正整数 的数组…

Storm实时流式计算系统(全解)——中

storm编程的基本概念-topo-spout-bolt 例如下&#xff1a; storm 编程接口-spout的结构及组件实现 storm编程案例-spout组件-实现 这是我的第一个组件&#xff08;spout组件继承BaseRichSput&#xff09;所有重写内部的三个方法&#xff0c;用于接收数据&#xff08;这里数据是…

辛格迪客户案例 | 祐儿医药科技GMP培训管理(TMS)项目

01 项目背景&#xff1a;顺应行业趋势&#xff0c;弥补管理短板 随着医药科技行业的快速发展&#xff0c;相关法规和标准不断更新&#xff0c;对企业的质量管理和人员培训提出了更高要求。祐儿医药科技有限公司&#xff08;以下简称“祐儿医药”&#xff09;作为一家专注于创新…

LeetCode 热题100 226. 翻转二叉树

LeetCode 热题100 | 226. 翻转二叉树 大家好&#xff0c;今天我们来解决一道经典的算法题——翻转二叉树。这道题在 LeetCode 上被标记为简单难度&#xff0c;要求我们翻转一棵二叉树&#xff0c;并返回其根节点。下面我将详细讲解解题思路&#xff0c;并附上 Python 代码实现…

论文阅读《 FEDERATED RECOMMENDATION WITH ADDITIVE PERSONALIZATION》

论文概况 本文是2024 ICLR的一篇联邦推荐论文&#xff0c;提出了 FedRAP&#xff0c;旨在解决联邦学习&#xff08;FL&#xff09;环境中的推荐系统挑战。其主要目标是提高推荐系统的个性化程度&#xff0c;同时减少通信成本&#xff0c;这在联邦学习系统中通常是一个重要问题…

10种方法教你又小又清晰地压缩视频

视频压缩是有可能会损失画质的&#xff0c;但也可以通过一些方法尽量减少画质损失。在有效压缩视频大小的同时&#xff0c;尽量控制视频压缩画质在人眼无法察觉的范围内。下面就从10个角度向大家介绍10个不同的视频压缩方法&#xff0c;并推荐相关的视频压缩软件&#xff0c;整…

微服务测试

微服务架构是一种将应用程序设计为一组小型、独立服务的方法,每个服务实现特定的业务功能,并通过定义良好的 API 进行通信。由于微服务架构的复杂性,测试微服务变得尤为重要。以下是一些微服务测试的实践和策略: 微服务测试的挑战 服务间的依赖:微服务之间存在复杂的依赖…

10.2 指针进阶_函数指针

指针进阶 5. 函数指针6. 函数指针数组7. 指向函数指针数组的指针8. 回调函数 10.1 指针进阶_数组指针 10.3 指针进阶_代码分析 5. 函数指针 void test() {printf("hehe\n"); } int main() {printf("%p\n", test);printf("%p\n", &test);re…