【算法】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.rhkb.cn/news/437532.html

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

相关文章

C语言语句、语句分类及注释

文章目录 一、语句和语句分类二、注释&#x1f355;注释是什么&#xff1f;为什么写注释&#xff1f;1. /**/的形式2. //的形式3. 注释会被替换 三、随机数的生成1.rand函数2.srand函数3.time函数4.设置随机数的范围 四、C99中的变长数组五、问题表达式解析表达式1表达式2表达式…

手机实时提取SIM卡打电话的信令声音-(题外、插播一条广告)

手机实时提取SIM卡打电话的信令声音-(题外、插播一条广告) 前言 在去年的差不多这个时候&#xff0c;我们做了一遍外置配件的选型&#xff0c;筛选过滤了一批USB蓝牙配件和type-c转usb的模块。详情可参考《外置配件的电商价格和下载链接的选型.docx》一文&#xff1a;蓝牙电话…

FireRedTTS - 小红书最新开源AI语音克隆合成系统 免训练一键音频克隆 本地一键整合包下载

小红书技术团队FireRed最近推出了一款名为FireRedTTS的先进语音合成系统&#xff0c;该系统能够基于少量参考音频快速模仿任意音色和说话风格&#xff0c;实现独特的音频内容创造。 FireRedTTS 只需要给定文本和几秒钟参考音频&#xff0c;无需训练&#xff0c;就可模仿任意音色…

Python基础语句教学

Python是一种高级的编程语言&#xff0c;由Guido van Rossum于1991年创建。它以简单易读的语法和强大的功能而闻名&#xff0c;被广泛用于科学计算、Web开发、数据分析等领域。 Python的应用领域广泛&#xff0c;可以用于开发桌面应用程序、Web应用、游戏、数据分析、人工智能等…

基于SSM的列车订票管理系统(含源码+sql+视频导入教程+文档+PPT)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1 、功能描述 基于SSM的列车订票管理系统3拥有两种角色&#xff1b;管理员、用户 管理员&#xff1a;用户管理、车票管理、购票指南管理、系统管理等 用户&#xff1a;发布帖子、登录注册、购票等 1.…

C++继承与菱形继承(一文了解全部继承相关基础知识和面试点!)

目的减少重复代码冗余 Class 子类(派生类) &#xff1a; 继承方式 父类&#xff08;基类&#xff09; 继承方式共有三种&#xff1a;公共、保护、私有 父类的私有成员private无论哪种继承方式都不可以被子类使用 保护protected权限的内容在类内是可以访问&#xff0c;但是在…

Python | Leetcode Python题解之第452题用最少数量的箭引爆气球

题目&#xff1a; 题解&#xff1a; class Solution:def findMinArrowShots(self, points: List[List[int]]) -> int:if not points:return 0points.sort(keylambda balloon: balloon[1])pos points[0][1]ans 1for balloon in points:if balloon[0] > pos:pos balloo…

ubuntu18.04安装教程

window分区 制作启动盘 插入 按F12进入启动选项页面&#xff0c;选择usb启动 选择install ubuntu 进入安装页面 选择中文&#xff08;简体&#xff09; 键盘布局选择英语&#xff08;美国&#xff09; 选择正常安装 等一小会儿 选择其他选项 分区 包括500mb系统分区 1000…

通信工程师笔记

第一章 1.支撑网是使业务网正常运行,增强网络功能,提供全网服务质量以满足用户要求的网络。 2.常见的有线通信线路包括&#xff08;1&#xff09;双绞线&#xff0c;&#xff08;2&#xff09;同轴电缆&#xff0c;&#xff08;3&#xff09;光纤等&#xff0c;无线通信线路是…

鼓组编曲:鼓编写技巧之进鼓加花编写

为了方便快速查阅和运用一些教程笔记&#xff0c;个人记性有时可能不是特别好&#xff0c;所以只能疯狂做笔记了&#xff0c;制作以下图文笔记&#xff0c;仅供参考…… 鼓组加花 鼓的变动 进鼓后然后就可以动次打次了 下面是2个底鼓的加花

北京市大兴区启动乐享生活 寻味大兴 美食嘉年华 系列促销费活动

北京市大兴区启动乐享生活 寻味大兴 系列促销费活动 区商务局副局长 兰莉 致开幕辞 区餐饮行业协会会长 董志明 介绍活动内容 2024年9月30日&#xff0c;由大兴区商务局主办、大兴区餐饮行业协会承办&#xff0c;并得到高米店街道和大兴绿地缤纷城大力支持的“乐享生活 寻味大…

we3.0里的钱包是什么?

we3.0里的钱包是什么&#xff1f; 在Web3.0的语境中&#xff0c;以太坊钱包是一种专为与以太坊区块链网络及其去中心化应用&#xff08;DApps&#xff09;交互而设计的数字钱包。这种钱包不仅支持用户存储、发送和接收以太币&#xff08;ETH&#xff09;&#xff0c;还允许用户…

(十七)、Mac 安装k8s

文章目录 1、Enable Kubernetes2、查看k8s运行状态3、启用 kubernetes-dashboard3.1、如果启动成功&#xff0c;可以在浏览器访问3.2、如果没有跳转&#xff0c;需要单独安装 kubernetes-dashboard3.2.1、方式一&#xff1a;一步到位3.2.2、方式二&#xff1a;逐步进行 1、Enab…

Java网络通信—TCP

1.客户端 2.服务端

系统安全 - Linux 安全模型及实践

文章目录 总览Linux安全Linux 安全模型用户层权限管理的细节多用户环境中的权限管理文件权限与目录权限 最小权限原则的应用Linux 系统中的认证、授权和审计机制认证机制授权机制审计机制 主机入侵检测系统&#xff08;HIDS&#xff09;_ Host-based Intrusion Detection Syste…

JavaWeb酒店管理系统(详细版)

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

MongoDB入门:安装及环境变量配置

一、安装MonggoDB Windows系统安装MongoDB 1、下载MongoDB安装包 访问MongoDB官方网站&#xff0c;选择与Windows系统相匹配的MongoDB Community Server版本进行下载。 Download MongoDB Community Server | MongoDB 2、安装MongoDB 双击下载好的安装包文件&#xff0c;根…

【数据结构】图论基础

文章目录 图的概念图的基本概念图的类型图的表示方法 图的相关基本概念1. 路径&#xff08;Path&#xff09;2. 连通性&#xff08;Connectivity&#xff09;3. 图的度&#xff08;Degree&#xff09;4. 子图&#xff08;Subgraph&#xff09;5. 生成树&#xff08;Spanning Tr…

docker pull 超时Timeout失败的解决办法

当国内开发者docker pull遇到如下提示时&#xff0c;不要惊讶 [rootvm /]# docker pull postgres Using default tag: latest Error response from daemon: Get "https://registry-1.docker.io/v2/": dial tcp 128.121.146.235:443: i/o timeout [rootvm /]# 自2024…

dcatadmin 自定义登录页面

一、问题&#xff1a; 在后台管理系统中&#xff0c;不同的项目想要不同的登录页面&#xff0c;但是框架自带的登录页面就只有一个。 解决&#xff1a; 由芒果系统改造的dcatadmin登录插件&#xff0c;实现一键安装改变登录页面。 项目介绍 基于Laravel和Vue的快速开发的后台管…