回溯算法(C/C++)
目录
一、组合问题
组合
组合剪枝
组合总和 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;}
};
相关文章:

回溯算法(C/C++)
目录 一、组合问题 组合 组合剪枝 组合总和 III编辑 组合总和编辑 组合总和 II 电话号码的字母组合编辑 二、分割问题 分割回文串 复原 IP 地址 三、集合问题 子集 子集 II 非递减子序列 四、排列问题 全排列 全排列 II 五、棋盘问题 N 皇后 课程&#x…...
物联网智慧农业一体化解决方案-可继续扩展更多使用场景
在智慧农业中,从种子、施肥、灌溉、锄地、农具管理、日常照料到蔬菜档案管理,以及与客户、供应商、市场的对接,可以通过物联网(IoT)、大数据、人工智能(AI)、区块链和云计算等技术,构建一个从生产到销售的全流程数字化、智能化农业生态系统。以下是实现方案和技术路径的…...
Jackson 详解
目录 前言 Jackson 是 Java 生态中最流行的 JSON 处理库之一,广泛应用于 RESTful API、数据存储和传输等场景。它提供了高效、灵活的 JSON 序列化和反序列化功能,支持注解、模块化设计和多种数据格式(如 XML、YAML)。本文将详细介…...

游戏引擎学习第143天
仓库:https://gitee.com/mrxiao_com/2d_game_3 回顾并规划今天的内容 目前,我们正在进行声音混合的开发。我们已经写好了声音混合器,并且已经实现了一些功能,比如声音流播放和音量插值。过去一周我们做了很多工作,进展非常快。不…...

SLAM评估工具安装及使用EVO(Ubuntu20.04安装evo)--缺少 onnx 库还有Pandas 版本不兼容解决
介绍一下我的是ubuntu20.04.机载电脑是orinnx,通过源码烧写的系统。 首先打开终端,输入 pip install evo --upgrade --no-binary evo 安装过程中出现如下问题 缺少 onnx 库还有Pandas 版本不兼容, ONNX(Open Neural Network E…...
Nginx解决前端跨域问题
1. 理解 CORS 和同源策略 1.1 同源策略 同源策略是一种浏览器安全机制,用于阻止不同源(不同域名、协议或端口)的 Web 应用相互访问数据。它确保了 Web 应用的隔离性,防止恶意网站访问用户数据或执行不安全的操作。 同源策略下&…...

ReferenceError: assignment to undeclared variable xxx
🤍 前端开发工程师、技术日更博主、已过CET6 🍨 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 🕠 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》、《前端求职突破计划》 🍚 蓝桥云课签约作者、…...

国产编辑器EverEdit - 宏功能介绍
1 宏 1.1 应用场景 宏是一种重复执行简单工作的利器,可以让用户愉快的从繁琐的工作中解放出来,其本质是对键盘和菜单的操作序列的录制,并不会识别文件的内容,属于无差别无脑执行。 特别是对一些有规律的重复按键动作,…...

图像滑块对比功能的开发记录
背景介绍 最近,公司需要开发一款在线图像压缩工具,其中的一个关键功能是让用户直观地比较压缩前后的图像效果。因此,我们设计了一个对比组件,它允许用户通过拖动滑块,动态调整两张图像的显示区域,从而清晰…...
【计算机网络】Socket
Socket 是网络通信的核心技术之一,充当应用程序与网络协议栈之间的接口。 1. Socket 定义 Socket(套接字)是操作系统提供的 网络通信抽象层,允许应用程序通过标准接口(如 TCP/IP 或 UDP)进行数据传输。它…...
Electron应用中获取设备唯一ID和系统信息
让我创建一篇关于如何在Electron应用中获取设备唯一ID和系统信息,并在登录时使用这些信息的博客文章。我将确保步骤明确、条理清晰,适合初学者和有经验的开发者。 这篇博客应包含以下部分: 介绍 - 为什么需要获取设备信息前提条件和安装依赖…...

文件上传漏洞:upload-labs靶场11-20
目录 pass-11 pass-12 pass-13 pass-14 pass-15 pass-16 pass-17 pass-18 pass-19 pass-20 pass-11 分析源代码 ,发现上传文件的存放路径可控 if(isset($_POST[submit])){$ext_arr array(jpg,png,gif);$file_ext substr($_FILES[upload_file][name],st…...

国产化板卡设计原理图:2330-基于FMC接口的JFM7K325T PCIeX4 3U PXIe接口卡
基于FMC接口的JFM7K325T PCIeX4 3U PXIe接口卡 一、板卡概述 本板卡基于 FPGAJFM7K325T 芯片,pin_to_pin兼容FPGAXC7K410T-2FFG900 ,支持PCIeX8、64bit DDR3容量2GByte,HPC的FMC连接器,板卡支持PXIE标准协议,其中XJ3…...

使用Open WebUI下载的模型文件(Model)默认存放在哪里?
🏡作者主页:点击! 🤖Ollama部署LLM专栏:点击! ⏰️创作时间:2025年2月21日21点21分 🀄️文章质量:95分 文章目录 使用CMD安装存放位置 默认存放路径 Open WebUI下…...

FPGA 配置原理
用户编程控制的FPGA 是通过加载比特位流配置内部的存储单元实现的。该存储单元就是所谓的配置单元,它必须在器件上电后进行配置,从而设置查找表(LUT)的属性、连线方式、IOB 电压标准和其它的用户设计。 1.配置帧 以Xilinx 公司的…...

企业级虚拟化数据库基础平台自动化部署项目
一、项目简介及准备工作 1.1.虚拟化平台简介 1.1.1.ESXi 8 是什么? 定义: ESXi 8 是 VMware 推出的最新版本 裸机虚拟化管理程序(Hypervisor),属于 VMware vSphere 产品线的核心组件。 核心功能: 在物理…...
关于elementui的时间组件与后端时间和oracle数据库时间的对应格式
前端: <el-date-pickerv-model"formInline.startTime"type"date"value-format"yyyy-MM-dd"placeholder"选择日期"> </el-date-picker> 后端: private String startTime; private String endTime…...

一周学会Flask3 Python Web开发-WTForms表单验证
锋哥原创的Flask3 Python Web开发 Flask3视频教程: 2025版 Flask3 Python web开发 视频教程(无废话版) 玩命更新中~_哔哩哔哩_bilibili 我们可以通过WTForms表单类属性的validators属性来实现表单验证。 常用的WTForms验证器 验证器说明DataRequired(messageNo…...

qt open3dBPA重建
qt open3dBPA重建 效果展示二、流程三、代码效果展示 二、流程 创建动作,链接到槽函数,并把动作放置菜单栏 参照前文 三、代码 1、槽函数实现 void on_actionBPA_triggered();//bpa重建 void MainWindow::...
Unity游戏开发中的网格简化与LOD技术(Mesh Simplification LOD)
在Unity游戏开发中,网格简化(Mesh Simplification)和LOD(Level of Detail)技术是优化渲染性能的关键手段,尤其在处理复杂场景和高精度模型时至关重要。以下是一套系统的实现方案与优化策略: 一、…...

地震勘探——干扰波识别、井中地震时距曲线特点
目录 干扰波识别反射波地震勘探的干扰波 井中地震时距曲线特点 干扰波识别 有效波:可以用来解决所提出的地质任务的波;干扰波:所有妨碍辨认、追踪有效波的其他波。 地震勘探中,有效波和干扰波是相对的。例如,在反射波…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】
微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来,Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...
Cesium1.95中高性能加载1500个点
一、基本方式: 图标使用.png比.svg性能要好 <template><div id"cesiumContainer"></div><div class"toolbar"><button id"resetButton">重新生成点</button><span id"countDisplay&qu…...
测试markdown--肇兴
day1: 1、去程:7:04 --11:32高铁 高铁右转上售票大厅2楼,穿过候车厅下一楼,上大巴车 ¥10/人 **2、到达:**12点多到达寨子,买门票,美团/抖音:¥78人 3、中饭&a…...

linux arm系统烧录
1、打开瑞芯微程序 2、按住linux arm 的 recover按键 插入电源 3、当瑞芯微检测到有设备 4、松开recover按键 5、选择升级固件 6、点击固件选择本地刷机的linux arm 镜像 7、点击升级 (忘了有没有这步了 估计有) 刷机程序 和 镜像 就不提供了。要刷的时…...
将对透视变换后的图像使用Otsu进行阈值化,来分离黑色和白色像素。这句话中的Otsu是什么意思?
Otsu 是一种自动阈值化方法,用于将图像分割为前景和背景。它通过最小化图像的类内方差或等价地最大化类间方差来选择最佳阈值。这种方法特别适用于图像的二值化处理,能够自动确定一个阈值,将图像中的像素分为黑色和白色两类。 Otsu 方法的原…...
相机Camera日志分析之三十一:高通Camx HAL十种流程基础分析关键字汇总(后续持续更新中)
【关注我,后续持续新增专题博文,谢谢!!!】 上一篇我们讲了:有对最普通的场景进行各个日志注释讲解,但相机场景太多,日志差异也巨大。后面将展示各种场景下的日志。 通过notepad++打开场景下的日志,通过下列分类关键字搜索,即可清晰的分析不同场景的相机运行流程差异…...
DeepSeek 技术赋能无人农场协同作业:用 AI 重构农田管理 “神经网”
目录 一、引言二、DeepSeek 技术大揭秘2.1 核心架构解析2.2 关键技术剖析 三、智能农业无人农场协同作业现状3.1 发展现状概述3.2 协同作业模式介绍 四、DeepSeek 的 “农场奇妙游”4.1 数据处理与分析4.2 作物生长监测与预测4.3 病虫害防治4.4 农机协同作业调度 五、实际案例大…...

基于 TAPD 进行项目管理
起因 自己写了个小工具,仓库用的Github。之前在用markdown进行需求管理,现在随着功能的增加,感觉有点难以管理了,所以用TAPD这个工具进行需求、Bug管理。 操作流程 注册 TAPD,需要提供一个企业名新建一个项目&#…...