【算法】DFS 系列之 穷举/暴搜/深搜/回溯/剪枝(上篇)

【ps】本篇有 9 道 leetcode OJ。 

目录

一、算法简介

二、相关例题

1)全排列

.1- 题目解析

.2- 代码编写

2)子集

.1- 题目解析

.2- 代码编写

3)找出所有子集的异或总和再求和

.1- 题目解析

.2- 代码编写

4)全排列 II

.1- 题目解析

.2- 代码编写

5)电话号码的字母组合

.1- 题目解析

.2- 代码编写

6)括号生成

.1- 题目解析

.2- 代码编写

7)组合

.1- 题目解析

.2- 代码编写

8)目标和

.1- 题目解析

.2- 代码编写

9)组合总和

.1- 题目解析

.2- 代码编写


一、算法简介

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

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

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

// 回溯算法的模板
void dfs(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]);// 做出当前选择后继续搜索dfs(path, choices);// 撤销选择path.pop_back();}
}

        其中, path 表示当前已经做出的选择, choices 表示当前可以做的选择。在回溯算法中,我们需要做出选择,然后递归地调用回溯函数。如果满足结束条件,则将当前路径添加到结果集中。

        否则,我们需要撤销选择,回到上一个状态,然后继续搜索其他的选择。回溯算法的时间复杂度通常较高,因为它需要遍历所有可能的解。但是,回溯算法的空间复杂度较低,因为它只需要维护一个状态树。在实际应用中,回溯算法通常需要通过剪枝等方法进行优化,以减少搜索的次数,从而提高算法的效率。

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

二、相关例题

1)全排列

46. 全排列

.1- 题目解析

        全排列的过程,其实可以画成一棵决策树,而找出全排列的结果,其实就是对这棵决策树进行 DFS。

         DFS 的思路是,循环模仿遍历树的结点,到叶子结点就返回,不是就进入循环。

        在下面 for 循环里要考虑这个位置要填哪个数。根据题目要求,我们肯定不能填已经填过的数,因此很容易想到的一个处理手段就是,定义一个标记数组来标记已经填过的数,那么在填这个数的时候,我们遍历题目给定的所有数,如果这个数没有被标记过,我们就尝试填入,并将其标记,继续尝试填下一个位置。而回溯的时候,要撤销这一个位置填的数以及标记,并继续尝试其他没被标记过的数。

.2- 代码编写

class Solution {vector<vector<int>> ret;vector<int> path;bool check[7];
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;}}}
};

2)子集

78. 子集

.1- 题目解析

        本题有两种解法。

        第一种与上一道题类似,根据某一个元素选或不选,将所有的子集穷举出来,然后统计结果即可。

        第二种解法则是根据子集中有多少个元素,来将所有的子集穷举出来。

.2- 代码编写

//解法一
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 i){if(i==nums.size()){ret.push_back(path);return;}//不选dfs(nums,i+1);//选path.push_back(nums[i]);//记录结果dfs(nums,i+1);path.pop_back();//恢复现场}
};
//解法二
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();        //回到当前层,恢复现场}}
};

3)找出所有子集的异或总和再求和

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

.1- 题目解析

        这道题只需要在上一道的基础上,稍微改变统计结果的方式即可。

.2- 代码编写

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

4)全排列 II

47. 全排列 II

.1- 题目解析

        我们可以直接在上文《全排列》的基础上用 set 对结果去重。

        或者,在上文《全排列》的基础上,加入剪枝操作。由于题目不要求返回的排列顺序,因此我们可以对初始状态排序,将所有相同的元素放在各自相邻的位置,方便之后操作。

.2- 代码编写

//解法一:set去重
class Solution {set<vector<int>> ret;vector<int> path;bool cheak[8] = {false};
public:vector<vector<int>> permuteUnique(vector<int>& nums) {dfs(nums);vector<vector<int>> tmp(ret.begin(), ret.end());return tmp;}void dfs(vector<int> nums){if(nums.size() == path.size()){// if(find(ret.begin(), ret.end(), path) == ret.end())//     ret.push_back(path);ret.insert(path);return;}for(int i = 0; i < nums.size(); ++i){if(cheak[i] == false) // 如果没有用过{path.push_back(nums[i]);cheak[i] = true;dfs(nums); // 此时路径已经加上一个了,在让其进入递归path.pop_back(); // 回溯,恢复现场,(递归往回走了)cheak[i] = false;}}}
};
//解法二:剪枝,关心不合法的分支
class Solution {vector<int> path;vector<vector<int>> ret;bool check[9];
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);return;}for(int i=0;i<nums.size();i++){if(check[i]==true|| (i!=0 && nums[i]==nums[i-1] && check[i-1]==false)){continue;}path.push_back(nums[i]);check[i]=true;dfs(nums);path.pop_back();check[i]=false;}}
};
//解法三:剪枝,关心合法的分支
//解法二:剪枝
class Solution {vector<int> path;vector<vector<int>> ret;bool check[9];
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);return;}for(int i=0;i<nums.size();i++){if(check[i]==false && (i==0 || nums[i]!=nums[i-1] || check[i-1]==true))//剪枝{path.push_back(nums[i]);check[i]=true;dfs(nums);path.pop_back();check[i]=false;}}}
};

5)电话号码的字母组合

17. 电话号码的字母组合

.1- 题目解析

        每一个数字都对应了一串字符,我们可以由此用一个哈希表建立数字和字符之间的映射,以便通过数字找到相应的字符。

        而其他过程同上文中的题目,通过画决策树 + DFS 来解决。

.2- 代码编写

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

6)括号生成

22. 括号生成

.1- 题目解析

.2- 代码编写

class Solution {int left,right,n;string path;vector<string> ret;
public:vector<string> generateParenthesis(int _n) {n=_n;dfs();return ret;}void dfs(){if(right==n){ret.push_back(path);return ;}if(left<n){path.push_back('(');left++;dfs();path.pop_back();left--;}if(right<left){path.push_back(')');right++;dfs();path.pop_back();right--;}}
};

7)组合

77. 组合

.1- 题目解析

        本题是上一道题的变形,画决策树穷举出所有情况即可。

.2- 代码编写

class Solution {vector<int> path;vector<vector<int>> ret;int n,k;
public:vector<vector<int>> combine(int _n, int _k) {n=_n,k=_k;dfs(1);return ret;}void dfs(int start){if(path.size()==k){ret.push_back(path);return;}for(int i=start;i<=n;i++){path.push_back(i);dfs(i+1);path.pop_back();}}
};

8)目标和

494. 目标和

.1- 题目解析

.2- 代码编写

class Solution {
int ret,aim;
public:int findTargetSumWays(vector<int>& nums, int target) {aim=target;dfs(nums,0,0);//参数:原始数组、当前下标位置、决策树某一条路径之和return ret;}void dfs(vector<int>& nums,int pos,int path){if(pos==nums.size()){if(path==aim)ret++;//统计结果return;}dfs(nums,pos+1,path+nums[pos]);//穷举加dfs(nums,pos+1,path-nums[pos]);//穷举减}
};

9)组合总和

39. 组合总和

.1- 题目解析

 

.2- 代码编写

//解法一:枚举每个值之和
class Solution {int aim;vector<int> path;vector<vector<int>> ret;
public:vector<vector<int>> combinationSum(vector<int>& nums, int target) {aim=target;dfs(nums,0,0);return ret;}void dfs(vector<int>& nums,int pos,int sum){if(sum==aim){ret.push_back(path);return;}if(sum>aim || pos==nums.size())return;//回溯for(int i=pos;i<nums.size();i++){path.push_back(nums[i]);dfs(nums,i,sum+nums[i]);path.pop_back();}}
};
//解法二:枚举每个值的个数
class Solution {int aim;vector<int> path;vector<vector<int>> ret;
public:vector<vector<int>> combinationSum(vector<int>& nums, int target) {aim=target;dfs(nums,0,0);return ret;}void dfs(vector<int>& nums,int pos,int sum){if(sum==aim){ret.push_back(path);return;}if(sum>aim || pos==nums.size())return;//回溯for(int k=0;k*nums[pos]<=aim;k++) //枚举个数{if(k)path.push_back(nums[pos]);dfs(nums,pos+1,sum+k*nums[pos]);}for(int k=1;k*nums[pos]<=aim;k++){path.pop_back();}}
};

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

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

相关文章

​​三SSH

ssh密钥对登录原理 &#xff1a;首先&#xff0c;客户端事先生成一对密钥&#xff0c;并将公钥保存在服务器上的授权文件中。接下来&#xff0c;客户端不用密码&#xff0c;而是用密钥对来验证身份。客户端用服务器的公钥来加密自己的公钥&#xff0c;然后把加密后的信息发送给…

数字化转型:开启未来发展新引擎

在当今飞速发展的时代&#xff0c;数字化转型已成为企业、组织乃至整个社会发展的关键趋势。 信息技术的迅猛发展&#xff0c;如互联网、大数据、人工智能等&#xff0c;为数字化转型提供了强大支撑。市场竞争的加剧&#xff0c;也促使企业不断寻求提升竞争力的方法&#xff0c…

利用git将项目上传到github

采用git而不是在pycharm中共享的原因&#xff1a;可能会出现上图报错 目录 1、创建github仓库2、在 git bash 中初始化Git仓库&#xff0c;添加文件&#xff0c;上传代码 1、创建github仓库 2、在 git bash 中初始化Git仓库&#xff0c;添加文件&#xff0c;上传代码

[Redis] 渐进式遍历+使用jedis操作Redis+使用Spring操作Redis

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏: &#x1f9ca; Java基本语法(97平均质量分)https://blog.csdn.net/2301_80050796/category_12615970.html?spm1001.2014.3001.5482 &#x1f355; Collection与…

LabVIEW提高开发效率技巧----利用第三方库和工具

LabVIEW开发不仅依赖于自身强大的图形化编程能力&#xff0c;还得益于其庞大的用户社区和丰富的第三方库。这些工具和库能够帮助开发者快速解决问题&#xff0c;提升开发效率&#xff0c;避免从头开始编写代码。 1. LabVIEW工具网络&#xff08;NI Tools Network&#xff09; …

DVWA-File Inclusion(文件包含)渗透测试

概念&#xff1a; 漏洞产生原因&#xff1a; 主要是由于开发人员没有对用户输入的文件路径进行严格的过滤和验证。例如&#xff0c;如果一个 Web 应用程序接受用户输入的文件路径&#xff0c;然后使用这个路径进行文件包含&#xff0c;而没有对用户输入进行任何检查&#xff0c…

传输层协议 --- UDP

序言 在之前的文章 Socket 编程 中&#xff0c;我们只是简单的知道了怎么利用 UDP协议 或者是 TCP协议 来发送我们的数据&#xff0c;并且我们还知道 UDP 是不可靠的&#xff0c;TCP 是可靠的。但这是为什么呢&#xff1f;底层的构造和策略决定他们的属性&#xff01;这篇文章中…

数据结构编程实践20讲(Python版)—01数组

本文目录 01 数组 arrayS1 说明S2 举例S3 问题&#xff1a;二维网格中的最小路径求解思路Python3程序 S4 问题&#xff1a;图像左右变换求解思路Python3程序 S5 问题&#xff1a;青蛙过河求解思路Python3程序 写在前面 数据结构是计算机科学中的一个重要概念&#xff0c;用于组…

HttpSession使用方法及原理

HttpSession使用方法及原理 一、HttpSession使用流程说明二、登录概述具体 三、访问过程概述具体 一、HttpSession使用流程说明 1.用户发送登录请求到服务器。 2.服务器处理登录请求&#xff0c;调用userService.login(loginUser)。 3.如果登录成功&#xff0c;服务器调用requ…

Linux云计算 |【第四阶段】NOSQL-DAY2

主要内容&#xff1a; Redis集群概述、部署Redis集群&#xff08;配置manage管理集群主机、创建集群、访问集群、添加节点、移除节点&#xff09; 一、Redis集群概述 1、集群概述 所谓集群&#xff0c;就是通过添加服务器的数量&#xff0c;提供相同的服务&#xff0c;从而让…

【echarts】报错series.render is required.

总结&#xff1a;就是echarts无法保存renderItem函数到json里&#xff0c;因为renderItem是个封装方法&#xff0c;因此需要初始化加载时重新插入renderItem即可 1.描述&#xff1a;控制台报错series.render is required. 原数据json如下&#xff1a; {type: "bar"…

工作安排 - 华为OD统一考试(E卷)

2024华为OD机试(C卷+D卷)最新题库【超值优惠】Java/Python/C++合集 题目描述 小明每周上班都会拿到自己的工作清单,工作清单内包含n项工作,每项工作都有对应的耗时时长(单位h)和报酬,工作的总报酬为所有已完成工作的报酬之和。那么请你帮小明安排一下工作,保证小明在指定…

科研绘图系列:R语言树结构聚类热图(cluster heatmap)

文章目录 介绍加载R包导入数据数据预处理画图修改图形导出数据系统信息介绍 热图结合树结构展示聚类结果通常用于展示数据集中的模式和关系,这种图形被称为聚类热图或层次聚类热图。在这种图中,热图部分显示了数据矩阵的颜色编码值,而树结构(通常称为树状图或聚类树)则显…

AIGAME背后的强大背景与AI币价值的崛起

AIGAME平台背后汇集了强大的资本和技术支持&#xff0c;凭借蒙特加密产业基金的战略投资和汇旺集团的多元化Web3基础设施建设&#xff0c;AIGAME在全球范围内迅速崛起。平台所使用的Sleepless AI技术&#xff0c;结合区块链与AI的深度融合&#xff0c;赋能AI币&#xff0c;使其…

虚拟社交的新时代:探索Facebook的元宇宙愿景

随着技术的不断进步&#xff0c;社交媒体的形态也在悄然变化。Facebook&#xff08;现名Meta&#xff09;正站在这一变革的前沿&#xff0c;积极探索元宇宙的愿景。元宇宙不仅是虚拟现实&#xff08;VR&#xff09;和增强现实&#xff08;AR&#xff09;的结合&#xff0c;更是…

遍历9个格子winmine!StepBlock和遍历8个格子winmine!StepBox的对决

遍历9个格子winmine!StepBlock和遍历8个格子winmine!StepBox的对决 第一部分&#xff1a;windbg调试记录。 0: kd> g Breakpoint 10 hit winmine!DoButton1Up: 001b:0100390e a130510001 mov eax,dword ptr [winmine!xCur (01005130)] 0: kd> kc # 00 winmine…

【RabbitMQ 项目】服务端:服务器模块

文章目录 一.编写思路二.代码实践三.服务端模块关系总结 一.编写思路 成员变量&#xff1a; muduo 库中的 TCP 服务器EventLoop 对象&#xff1a;用于主线程循环监控连接事件协议处理句柄分发器&#xff1a;用于初始化协议处理器&#xff0c;便于把不同请求派发给不同的业务处理…

Golang | Leetcode Golang题解之第433题最小基因变化

题目&#xff1a; 题解&#xff1a; func diffOne(s, t string) (diff bool) {for i : range s {if s[i] ! t[i] {if diff {return false}diff true}}return }func minMutation(start, end string, bank []string) int {if start end {return 0}m : len(bank)adj : make([][…

OpenHarmony标准系统mipi摄像头适配

OpenHarmony标准系统mipi摄像头适配 本文档以rk3568为例&#xff0c;讲述如何在OpenHarmony 标准系统rk设备上适配mipi摄像头。 开发环境 OpenHarmony标准系统4.1rrk3568设备摄像头ov5648,ov8858 文档约定&#xff1a;4.1r_3568为OpenHarmony标准系统源码根目录 1.适配准备:得…

树莓派pico上手

0 介绍 不同于作为单板计算机的树莓派5&#xff0c;树莓派 pico 是一款低成本、高性能的微控制器板&#xff0c;具有灵活的数字接口。主要功能包括&#xff1a; 英国树莓派公司设计的 RP2040 微控制器芯片双核 Arm Cortex M0 处理器&#xff0c;弹性的时钟频率高达 133 MHz26…