【回溯算法经典题目解析】

1. 什么是回溯算法

回溯算法是⼀种经典的递归算法,通常用于解决组合问题、排列问题和搜索问题等。

回溯算法的基本思想:从一个初始状态开始,按照⼀定的规则向前搜索,当搜索到某个状态⽆法前进时,回退到前⼀个状态,再按照其他的规则搜索。回溯算法在搜索过程中维护一个状态树,通过遍历状态树来实现对所有可能解的搜索。

回溯算法的核心思想:“试错”,即在搜索过程中不断地做出选择,如果选择正确,则继续向前搜 索;否则,回退到上一个状态,重新做出选择。回溯算法通常⽤于解决具有多个解,且每个解都需要 搜索才能找到的问题。

2. 回溯算法的模板

void backtrack(vector<int>& path, vector<int>& choice, ...) {// 满足结束条件if (/*满足结束条件*/) {// 将路径添加到结果集中res.push_back(path);return;}// 遍历所有选择for (int i = 0; i < choices.size(); i++) {// 做出选择path.push_back(choices[i]);// 做出当前选择后继续搜索}backtrack(path, choices);// 撤销选择path.pop_back();
}

其中, path 表⽰当前已经做出的选择, choices 表⽰当前可以做的选择。在回溯算法中,我们需 要做出选择,然后递归地调⽤回溯函数。如果满⾜结束条件,则将当前路径添加到结果集中;否则, 我们需要撤销选择,回到上⼀个状态,然后继续搜索其他的选择。 回溯算法的时间复杂度通常较高,因为它需要遍历所有可能的解。但是,回溯算法的空间复杂度较低,因为它只需要维护⼀个状态树。在实际应用中,回溯算法通常需要通过剪枝等方法进⾏优化,以减少搜索的次数,从⽽提高算法的效率。

3. 回溯算法的应用

  • 组合问题

  • 排列问题

  • 子集问题

总结

回溯算法是⼀种⾮常重要的算法,可以解决许多组合问题、排列问题和搜索问题等。回溯算法的核心思想是搜索状态树,通过遍历状态树来实现对所有可能解的搜索。回溯算法的模板非常简单,但是实现起来需要注意⼀些细节,比如如何做出选择、如何撤销选择等。

4. 全排列

首先我们看到这个题目,立马就会想到使用穷举来解决,依次选取一个位置来枚举,但是假如我们这个题目给的数组是100个,那么岂不要写100个for循环来枚举,太麻烦了,我们使用dfs来解决,使用dfs之前我们首先要画出我们的决策树,我们以实例一为例。

 此时就要用到我们的递归方法,当我们使用递归的时候,我们仅需关心某一个节点在干什么事情,只要我们当前的节点没有使用过,我们就可以添加进去,同时还要注意几个细节问题,

那我们就可以直接来写代码了。

class Solution {vector<int> path;vector<vector<int>> ret;bool check[7] = {false}; // 标记下标是否已经使用
public:vector<vector<int>> permute(vector<int>& nums) {dfs(nums);return ret;}void dfs(vector<int>& nums){if(path.size() == nums.size()){ret.push_back(path);return;}for(int i = 0; i < nums.size(); i++){if(check[i] == false){path.push_back(nums[i]);check[i] = true;dfs(nums);// 回溯 -> 恢复现场path.pop_back();check[i] = false;}}}
};

5. 子集

经过之前的全排列我们可以知道,要想解决一个递归的题目,我们就必须要画好我们的决策树,决策树画的越详细,题目越容易做出来,这个题目有两种决策树,我们分别画出来分别写一下代码。第一个决策树是根据元素选与不选来抉择的。

此时我们设计dfs函数的时候,有两个参数dfs(nums, i),nums就表示数组,而i就是表示是否选择这个下标对应的值,当我们选择的时候,就可以直接将nums[i]添加到path中,然后递归到dfs(nums, i+1)下一层,如果不选,就直接递归dfs(nums, i+1)下一层,当我们到叶子节点的时候,就可以返回啦!

class Solution {vector<vector<int>> ret;vector<int> path;
public:vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos){if(pos == nums.size()){ret.push_back(path);return;}// 选path.push_back(nums[pos]);dfs(nums, pos + 1);path.pop_back(); // 恢复现场// 不选dfs(nums, pos + 1);}
};

我们在来画出另外一种决策树,依据每层元素的个数来画,每次选完当前元素时,就从后面元素继续选择。

根据上面的决策树,每一层的节点都是枚举后面的数,所以我们的函数设计时dfs(nums, pos),pos当前层枚举的数,此时我们就要来一个for循环解决,同时我们还能发现此时我们不需要bool数组来检查元素是否被使用过,因为我们通过pos控制每一层的节点都是枚举后面的数,不会美剧到已经使用过的值,那什么时候统计结果呢?在进入for循环之前就可以直接统计结果啦。

class Solution {vector<vector<int>> ret;vector<int> path;
public:vector<vector<int>> subsets(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos){ret.push_back(path);for(int i = pos; i < nums.size(); i++){path.push_back(nums[i]); dfs(nums, i + 1);path.pop_back(); // 恢复现场}}
};

6. 找出所有子集的异或总和再求和

这个题目其实就是子集题目的变种题目,我们依然是使用的dfs来求出子集,只不过此时我们不需要再创建数组存储每个子集,只需要使用一个变量path存储子集的异或结果和一个变量ret存储所有子集的疑惑和结果即可。

class Solution {int ret = 0;int path = 0;
public:int subsetXORSum(vector<int>& nums) {dfs(nums, 0);return ret;}void dfs(vector<int>& nums, int pos){ret += path;for(int i = pos; i < nums.size(); i++){path ^= nums[i];dfs(nums, i + 1);path ^= nums[i]; // 恢复现场}}
};

7. 全排列 Ⅱ

首先我们这个题目是全排列的一个变种题目,这个题目中要求全排列之后不能出现重复的值,因此我们可以按照上面求全排列的方法求出结果,然后再利用set容器进行去重来解决这个题目,但是比较繁琐,我们可以利用剪枝来求解,我们先画出我们的决策树。

此时我们就可以有两种判断方法,只关心合法的分支,合法我们才去dfs,合法的肯定是当前位置并没有被使用,所以check[i] == false,同时我们还要满足,nums[i]不能和nums[i-1]位置的元素相等,但是我们可以看到左边的分支,如果check[i-1]的位置是已经被使用了,那么虽然nums[i]和nums[i-1]位置的元素相等,我们仍然可以使用这个元素,因为这两个元素不在同一层,最后还有一个条件,如果i==0,那么此时我们肯定是合法的,此时可以直接添加。

此时我们就可以来直接写代码啦!

class Solution {vector<vector<int>> ret;vector<int> path;bool check[8] = { false };
public:vector<vector<int>> permuteUnique(vector<int>& nums) {sort(nums.begin(), nums.end()); // 先排序dfs(nums);return ret;}void dfs(vector<int>& nums){if(path.size() == nums.size()){ret.push_back(path);}for(int i = 0; i < nums.size(); i++){// 剪枝if((i == 0 || nums[i-1] != nums[i] || check[i - 1] == true) && check[i] == false){path.push_back(nums[i]);check[i] = true;dfs(nums);// 恢复现场path.pop_back();check[i] = false;}}}
};

8. 电话号码的字母组合

看到这个题目,我们首先想到的就是for循环去解决问题,但是如果传入的digits特别多,那么就要使用很多的for循环,因此这个题目我们也可以使用dfs来解决,首先我们要做的就是画出我们的决策树。

首先我们就需要根据电话进行数字与字母的映射,我们这里直接创建一个数组即可,随后的工作就和全排列的思路差不多,直接上手代码。

class Solution {string hash[10] = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};string path;vector<string> ret;
public:vector<string> letterCombinations(string digits) {if(digits.empty())return ret;dfs(digits, 0);return ret;}void dfs(string& digits, int pos){if(pos == digits.size()){ret.push_back(path);return;}string tmp = hash[digits[pos] - '0'];for(int i = 0; i < tmp.size(); i++){path += tmp[i];dfs(digits, pos + 1);path.pop_back(); // 恢复现场}}
};

9. 括号生成

对于这样的问题,我们首先要了解的就是什么是有效的括号组合呢?第一就是左括号的数量等于右括号的数量,其次是从头开始的任意一个字串,左括号的数量大于右括号的数量。接下来我们就要做的就是画出决策树,决策树画的越详细,题目越容易做出来。

随后我们就可以直接来手写代码啦!!!

class Solution {vector<string> ret;string path;int left = 0;int right = 0;int n1 = 0;
public:vector<string> generateParenthesis(int n) {n1 = n;dfs();return ret;}void dfs(){if(n1 == right){ret.push_back(path);return;}// 选左括号// 左括号的数量小于nif(left < n1){path += '(';left++;dfs();path.pop_back(); // 恢复现场left--;}// 选右括号// 右括号的数量小于左括号的数量if(left > right){path += ')';right++;dfs();path.pop_back(); // 恢复现场right--;}}
};

10. 组合

这个题目其实就是上面的子集的变种题目,我们依然是先来画出我们的决策树。

随后我们就直接来写代码啦。

class Solution {vector<vector<int>> ret;vector<int> path;int n1, k1;
public:vector<vector<int>> combine(int n, int k) {n1 = n;k1 = k;dfs(0);return ret;}void dfs(int pos){if(k1 == path.size()){ret.push_back(path);return;}for(int i = pos; i < n1; i++){path.push_back(i + 1);dfs(i + 1);path.pop_back(); // 恢复现场}}
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/39323.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux下的蓝牙开发

参考&#xff1a; bluetooth在linux应用开发 - yuxi_o - 博客园 (cnblogs.com) linux下蓝牙开发(bluez应用) - 那一抹风 - 博客园 (cnblogs.com) Linux内核蓝牙子系统架构 HCI层 / HCl连接 / L2CAP / BNEP / 蓝牙数据包接收架构_哔哩哔哩_bilibili 【蓝牙】一文入门Bluez的…

cesium 聚合

cesium 聚合(下面附有源码) 示例代码 <html lang="en"><head><!-- Use correct character set. -->

【智能算法】目标检测算法

目录 一、目标检测算法分类 二、 常见目标检测算法及matlab代码实现 2.1 R-CNN 2.1.1 定义 2.1.2 matlab代码实现 2.2 Fast R-CNN 2.2.1 定义 2.2.2 matlab代码实现 2.3 Faster R-CNN 2.3.1 定义 2.3.2 matlab代码实现 2.4 YOLO 2.4.1 定义 2.4.2 matlab代码实现…

电信NR零流量小区处理

【摘要】随着目前网络建设逐步完善&#xff0c;5G用户的不断发展&#xff0c;针对零流量小区的分析及处理存在着必要性&#xff0c;零流量小区的出现既是用户分布及行为的直观体现&#xff0c;也是发展用户的一个指引&#xff0c;同时也能发现设备的一些故障。一个站点的能够带…

【数值计算库-超长笔记】Python-Mpmath库:高精度数值计算

原文链接&#xff1a;https://www.cnblogs.com/aksoam/p/18279394 更多精彩&#xff0c;关注博客园主页&#xff0c;不断学习&#xff01;不断进步&#xff01; 我的主页 csdn很少看私信&#xff0c;有事请b站私信 博客园主页-发文字笔记-常用 有限元鹰的主页 内容&#xf…

燃料电池混合电源的能量管理系统

这个例子显示了燃料电池混合电源的能量管理系统。 这个例子展示了燃料电池混合电源的能量管理系统。 电路描述 本文给出了基于燃料电池的多电动飞机应急动力系统的仿真模型。随着MEA中起落架和飞控系统的电气化程度的提高&#xff0c;常规应急电源系统(冲压式空气涡轮或空气驱…

希尔伯特基定理

【引理1】&#xff08;Dickson定理&#xff09; 对于 ∀ A ⊂ N n \forall A \subset \mathbb{N}^{n} ∀A⊂Nn&#xff0c; ∃ \exists ∃有限非空集 B ⊂ A B \subset A B⊂A&#xff0c;使得 ⟨ x A ⟩ ⟨ x B ⟩ \left\langle x^{A} \right\rangle \left\langle x^{B} \ri…

代码随想录算法训练营第20天 | 题目: 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点

代码随想录算法训练营第20天 | 题目&#xff1a; 235. 二叉搜索树的最近公共祖先 701.二叉搜索树中的插入操作 450.删除二叉搜索树中的节点 文章来源&#xff1a;代码随想录 题目名称&#xff1a; 235. 二叉搜索树的最近公共祖先 给定一个二叉搜索树, 找到该树中两个指定节点的…

强对抗的SquidLoader针对中国企业发起攻击

2024 年 4 月下旬&#xff0c;研究人员观察到一些特别的钓鱼邮件附件&#xff0c;文件名为「华为工业级路由器相关产品介绍和优秀客户案例」。顺藤摸瓜发现一系列以中国企业/组织名称命名的恶意附件&#xff0c;如「中国移动集团XX分公司」、「嘉X智能科技」与「XX水利技术学院…

Centos7部署Mysql8.0超级详细教程,一看就会!

1、准备 下载 Mysql 安装包源信息,去到这个网址&#xff0c;https://dev.mysql.com/downloads/repo/yum/ 复制红色框的内容&#xff0c; 2、开始安装 # 以下所有操作建议切换到 root 用户后运行。。yum install wget -y# 将上面的复制内容粘贴到后面&#xff0c;格式&…

前端性能优化-实测

PageSpeed Insights 性能测试 今天测试网站性能的时候发现一个问题&#xff0c;一个h2标签内容为什么会占据这么长的渲染时间&#xff0c;甚至有阶段测到占据了7000多毫秒&#xff0c;使用了很多方法都不能解决&#xff0c;包括了修改标签&#xff0c;样式大小等&#xff0c;当…

在Spring Boot项目中引入本地JAR包的步骤和配置

在Spring Boot项目中&#xff0c;有时需要引入本地JAR包以便重用已有的代码库或者第三方库。本文将详细介绍如何在Spring Boot项目中引入本地JAR包的步骤和配置&#xff0c;并提供相应的代码示例。 1. 为什么需要本地JAR包 在开发过程中&#xff0c;可能会遇到以下情况需要使…

JAVA连接FastGPT实现流式请求SSE效果

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; 一、先看效果 真正实流式请求&#xff0c;SSE效果&#xff0c;SSE解释&am…

CentOS7环境下DataX的安装、使用及问题解决

DataX概述 DataX 是阿里巴巴开源的一个异构数据源离线同步工具&#xff0c;致力于实现包括关系型数据库(MySQL、Oracle等)、HDFS、Hive、ODPS、HBase、FTP等各种异构数据源之间稳定高效的数据同步功能。 为了解决异构数据源同步问题&#xff0c;DataX将复杂的网状的同步链路变…

DNS解析过程及常见问题解决

DNS解析过程及常见问题解决 大家好&#xff0c;我是免费搭建查券返利机器人省钱赚佣金就用微赚淘客系统3.0的小编&#xff0c;也是冬天不穿秋裤&#xff0c;天冷也要风度的程序猿&#xff01; 什么是DNS解析&#xff1f; 在计算机网络中&#xff0c;DNS&#xff08;Domain N…

什么是字符串常量池?如何利用它来节省内存?

字符串常量池是Java中一个非常重要的概念&#xff0c;尤其对于理解内存管理和性能优化至关重要。想象一下&#xff0c;你正在管理一家大型图书馆&#xff0c;每天都有无数读者来借阅书籍。 如果每本书每次借阅都需要重新印刷一本&#xff0c;那么图书馆很快就会陷入混乱&#…

eclipse断点调试(用图说话)

eclipse断点调试&#xff08;用图说话&#xff09; debug方式启动项目&#xff0c;后端调试bug调试 前端代码调试&#xff0c;请参考浏览器断点调试&#xff08;用图说话&#xff09; 1、前端 选中一条数据&#xff0c;点击删除按钮 2、后端接口打断点 断点按钮 介绍 resum…

236、二叉树的最近公共祖先

前提&#xff1a; 所有 Node.val 互不相同 。p ! qp 和 q 均存在于给定的二叉树中。 代码如下&#xff1a; class Solution { public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root q || root p || root NULL) return root;TreeN…

.NET周刊【6月第5期 2024-06-30】

国内文章 呼吁改正《上海市卫生健康信息技术应用创新白皮书》 C# 被认定为A 组件 的 错误认知 https://www.cnblogs.com/shanyou/p/18264292 近日&#xff0c;《上海市卫生健康“信息技术应用创新”白皮书》发布&#xff0c;提到医疗信创核心应用适配方法及公立医院信息系统…

书城在线系统:基于Java和SSM框架的高效信息管理平台

开头语&#xff1a;你好呀&#xff0c;我是计算机学长猫哥&#xff01;如果有相关需求&#xff0c;文末可以找到我的联系方式。 开发语言&#xff1a;Java 数据库&#xff1a;MySQL 技术&#xff1a;SSM框架&#xff08;Spring, Spring MVC, Mybatis&#xff09; 工具&…