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

news2024/9/27 11:57:07

【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.coloradmin.cn/o/2169908.html

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

相关文章

PostgreSQL 17 发布了!非常稳定的版本

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 作者&#xff1a;IT邦德 中国DBA联盟(ACDU)成员&#xff0c;10余年DBA工作经验&#xff0c; Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主&#xff0c;全网粉丝10万 擅长主流Oracle、My…

springboot+大数据基于数据挖掘的招聘信息可视化大屏系统【内含源码+文档+部署教程】

博主介绍&#xff1a;✌全网粉丝10W,前互联网大厂软件研发、集结硕博英豪成立工作室。专注于计算机相关专业毕业设计项目实战6年之久&#xff0c;选择我们就是选择放心、选择安心毕业✌ &#x1f345;由于篇幅限制&#xff0c;想要获取完整文章或者源码&#xff0c;或者代做&am…

以到手价为核心的品牌电商价格监测

在当今竞争激烈的电商时代&#xff0c;品牌的价格监测至关重要。传统的页面价监测已无法满足品牌对渠道管控的需求&#xff0c;而到手价监测则成为品牌控价的关键所在。 力维网络&#xff0c;作为深耕数据监测服务多年的专业机构&#xff0c;拥有自主开发的数据监测系统&#…

对spring框架的搭建进行封装---springboot

目录 一.回顾spring 二.springboot概述 三.springboot的特点 四.springboot环境搭建 五.springboot配置文件 六.springboot数据访问管理 七.springboot注解 八.springboot集成mybatis 九.springboot全局异常捕获与处理 一.回顾spring 优点 开源,轻量级,非侵入式的一站式…

Springboot + netty + rabbitmq + myBatis

目录 0.为什么用消息队列1.代码文件创建结构2.pom.xml文件3.三个配置文件开发和生产环境4.Rabbitmq 基础配置类 TtlQueueConfig5.建立netty服务器 rabbitmq消息生产者6.建立常规队列的消费者 Consumer7.建立死信队列的消费者 DeadLetterConsumer8.建立mapper.xml文件9.建立map…

Visual Studio导出动态库

1、创建新项目&#xff0c;选择如下 2、工程目录结构如下 3、编写pch.h文件&#xff0c;内容如下 // pch.h: 这是预编译标头文件。 // 下方列出的文件仅编译一次&#xff0c;提高了将来生成的生成性能。 // 这还将影响 IntelliSense 性能&#xff0c;包括代码完成和许多代码浏…

AI驱动TDSQL-C Serverless 数据库技术实战营-融合智能体与TDSQL-C技术,高效实现二手房数据查询与分析应用

文章目录 什么是TDSQL-C技术创新算力服务器与数据库服务器申请与部署购买 TDSQL-C Mysql Serverless 实例购买HAI高算力服务器 准备工作准备数据下载依赖 案例研发创建数据库写入数据智能体与TDSQL-C 的结合应用第一步配置llama3.1第二步代码开发运行应用测试应用 总结 什么是T…

戏曲多多 1.0.6.0 专为电视端设计的戏曲与生活内容APP,同样适用于安卓手机,方便老年人使用

戏曲多多是一款专为电视端设计的应用程序&#xff0c;也可安装在安卓手机上&#xff0c;特别适合不会使用手机的老年人。该应用包含戏曲、红歌、儿歌、老电影等丰富内容&#xff0c;非常适合老年人的观看喜好。此外&#xff0c;还有广场舞教学、养生太极教程&#xff08;包括太…

Qualitor processVariavel.php 未授权命令注入漏洞复现(CVE-2023-47253)

0x01 漏洞概述 Qualitor 8.20及之前版本存在命令注入漏洞,远程攻击者可利用该漏洞通过PHP代码执行任意代码。 0x02 复现环境 FOFA&#xff1a;app"Qualitor-Web" 0x03 漏洞复现 PoC GET /html/ad/adpesquisasql/request/processVariavel.php?gridValoresPopHi…

3 pyqt5 Layout布局(保证主界面缩放各组件也对应缩放)== 主要有Qt Designer和完全代码设置两种设计方式(根据自己情况选择即可)

文章目录 前言一、Layout的类别二、使用Qt Designer进行Layout布局三、完全使用代码进行Layout布局前言 本节我们的http测试的例子,只实现界面方面的逻辑,底层不用管。我们主要的目的是通过这个例子设计界面布局。 我们前面写的界面程序有个问题,如果你用鼠标拖拽主窗口边…

雷达系统中杂波信号的建模与仿真

雷达系统中杂波信号的建模与仿真 2 杂波建模与模拟方法 2.1 杂波建模 杂波可以说是雷达在所处环境中接收到的不感兴趣的回波[4]。就像目标回波一样&#xff0c;杂波也是极为复杂的。为了有效地克服杂波对信号检测的影响&#xff0c;需要知道杂波的幅度特性以及频谱特性。除独…

JavaScript爬虫:数据抓取的艺术与实践

在当今数据驱动的世界中&#xff0c;JavaScript作为一种广泛使用的编程语言&#xff0c;不仅在前端开发中占据重要地位&#xff0c;也可以用于编写爬虫程序&#xff0c;从互联网上抓取有价值的数据。本文将介绍如何使用JavaScript编写爬虫程序&#xff0c;并探讨其在数据抓取中…

Harmony next Native API 开发工程实践

旨在通过文档&#xff0c;了解华为native开发中的关键节点&#xff0c;能够搭建一个比较实用的native功能、 目录 前言 一、napi是什么&#xff1f; 二、简单开始一个napi的实例 1.适配架构配置 2.新增实现 3.官方实例 4.napi与ark通讯的几种方式 Native侧的CallNative Native …

从耐用到防水:全面综合评估SD卡的性能指标

SD卡&#xff08;Secure Digital Memory Card&#xff09;是一种广泛使用的存储器件&#xff0c;因其快速的数据传输速度、可热插拔的特性以及较大的存储容量&#xff0c;广泛应用于各种场景&#xff0c;例如在便携式设备如智能手机、平板电脑、运动相机等&#xff0c;用于存储…

Docker镜像、Spark支持多表...Apache SeaTunnel 2.3.8版本将带来的惊喜

Apache SeaTunnel 2.3.8版本即将于大家见面&#xff0c;近日&#xff0c;Apache SeaTunnel PMC Member 范佳在社区的交流会上为大家提前透露了关于这个新版本即将进行的功能与特性更新概况&#xff0c;详细内容如下&#xff1a; SeaTunnel 简介 SeaTunnel是一个高性能的开源分…

【生物服务器】DAP-seq与H3K4me3 ChIP-seq服务,推动表观遗传学研究的创新工具

查看全文>>>探索基因调控新维度&#xff1a;汇智生物的DAP-seq与H3K4me3 ChIP-seq服务&#xff0c;推动表观遗传学研究的创新工具与合作案例 北京汇智精研生物科技由毕业于中国科学院、北京大学肿瘤医院、中国农科院、中国农业大学等科研院所的国家高精尖人才发起&…

计算机网络--HTTP协议

1.TCP,UDP的对比图 TCP:面向连接的,可靠的,字节流服务; UDP:无连接的,不可靠的,数据报服务; 2.补充网络部分的其他知识点 1).复位报文段 在某些特殊条件下&#xff0c; TCP 连接的一端会向另一端发送携带 RST 标志的报文段&#xff0c;即复位报文段&#xff0c;已通知对方…

【通知】“长三角档案数字资源长期保存与数据安全治理”专题培训

关注我们 - 数字罗塞塔计划 - 为加强长三角地区档案数字资源长期安全管理&#xff0c;提升档案管理人员档案信息化水平和实务技能&#xff0c;推动长三角地区档案数字化转型向纵深发展&#xff0c;上海市档案服务和教育中心将于近期举办“长三角档案数字资源长期保存与数据安全…

【裸机装机系列】16.kali(ubuntu)-安装linux和win双系统-重装win11步骤

推荐阅读&#xff1a; 1.kali(ubuntu)-为什么弃用ubuntu&#xff0c;而选择基于debian的kali操作系统 注意&#xff1a; 要先装windows&#xff0c;再装linux&#xff0c;不然linux的启动分区会被覆盖掉。为什么双系统要先装windows呢&#xff1f; 在一个新硬盘上&#xff0…

旺店通ERP集成金蝶K3(金蝶K3主供应链)

源系统成集云目标系统 金蝶K3介绍 金蝶K3是一款ERP软件&#xff0c;它集成了供应链管理、财务管理、人力资源管理、客户关系管理、办公自动化、商业分析、移动商务、集成接口及行业插件等业务管理组件。以成本管理为目标&#xff0c;计划与流程控制为主线&#xff0c;通…