【算法】dfs



快乐的流畅:个人主页


个人专栏:《算法神殿》《数据结构世界》《进击的C++》

远方有一堆篝火,在为久候之人燃烧!

文章目录

  • 引言
  • 一、全排列
    • 1. 决策树
    • 2. 设计代码
      • 1. 全局变量
      • 2. dfs函数
      • 3. 细节问题
  • 二、子集
    • 解法一
    • 1. 决策树
    • 2. 设计代码
      • 1. 全局变量
      • 2. dfs函数
      • 3. 细节问题
    • 解法二
    • 1. 决策树
    • 2. 设计代码
      • 1. 全局变量
      • 2. dfs函数
      • 3. 细节问题
  • 三、子集的异或总和之和
  • 四、全排列 ||
  • 五、电话号码的字母组合
  • 六、括号生成
  • 七、组合
  • 八、目标和
  • 九、组合总和
  • 十、字母大小写全排列
  • 十一、优美的排列
  • 总结

引言

在实际的dfs问题中,大多时候并不会直接告诉你,而是需要自己发现可以使用dfs来解决。而是否能用dfs解决的关键,就是画出决策树!同时,不同的决策树代表不同的解决方式,对于同一问题,好的决策树往往能节省时间,提高效率。

一、全排列

1. 决策树


绿色部分,就是剪枝,因为全排列不能重复枚举。

2. 设计代码

1. 全局变量

  • vector<vector< int >> ret:用来保存最终所有的结果
  • vector< int > path:用来保存单一路径的结果
  • bool check[6]:用来实现剪枝

2. dfs函数

  • 将数组中所有数枚举一遍,如果没有枚举过,则将其加入path

3. 细节问题

  • 回溯
    • 清除path中最后一个数
    • 更改check中的标记
  • 剪枝:根据check的标记,去除重复枚举的情况
  • 递归出口:当path路径长度等于枚举数组长度,则将其加入ret,返回
class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
    bool check[6];//实现剪枝
public:
    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])//剪枝
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                //回溯 - 恢复现场
                path.pop_back();
                check[i] = false;
            }
        }
    }

    vector<vector<int>> permute(vector<int>& nums)
    {
        dfs(nums);
        return ret;
    }
};

二、子集

解法一

1. 决策树

2. 设计代码

1. 全局变量

  • vector<vector< int >> ret:用来保存最终所有的结果
  • vector< int > path:用来保存单一路径的结果

2. dfs函数

  • 对于数组中的每一个数,都遵循选或不选两种方式
    • 不选:直接dfs下一层
    • 选:先将其加入path,再dfs下一层
  • dfs(nums, i):增加参数i作为当前数的下标

3. 细节问题

  • 回溯:删除path中最后一个数
  • 递归出口:当i枚举完所有数,来到数组末尾,则将path加入ret,返回
class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
public:
    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();
    }

    vector<vector<int>> subsets(vector<int>& nums)
    {
        dfs(nums, 0);
        return ret;
    }
};

解法二

1. 决策树


由于决策树不同的选取,解法二要优于解法一

2. 设计代码

1. 全局变量

  • vector<vector< int >> ret:用来保存最终所有的结果
  • vector< int > path:用来保存单一路径的结果

2. dfs函数

  • 按照集合中元素的个数进行分类
  • 依据集合的互异性,每层dfs只能选取下标i后面的数
  • dfs(nums, i):增加参数i作为当前数的下标

3. 细节问题

  • 回溯:删除path中最后一个数
  • 递归出口:每一层path都是结果,都需要添加到ret
class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
public:
    void dfs(vector<int>& nums, int i)
    {
        ret.push_back(path);

        for(int j=i; j<nums.size(); ++j)
        {
            path.push_back(nums[j]);
            dfs(nums, j+1);
            path.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums)
    {
        dfs(nums, 0);
        return ret;
    }
};

三、子集的异或总和之和


思路:子集的变式题(按照集合元素个数分类)

class Solution
{
    int ret = 0;
    int path = 0;
public:
    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];
        }
    }

    int subsetXORSum(vector<int>& nums)
    {
        dfs(nums, 0);
        return ret;
    }
};

四、全排列 ||


思路:本题是全排列的进阶版,存在重复元素,所以剪枝是关键。

  • 前提:先对数组排序
  • 同一个元素只能使用一次(check)
  • 对于每一个节点,相同的元素只能选一次
class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
    bool check[8];
public:
    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] && (i == 0 || nums[i] != nums[i-1] || check[i-1]))
            {
                path.push_back(nums[i]);
                check[i] = true;
                dfs(nums);
                check[i] = false;
                path.pop_back();
            }
        }
    }

    vector<vector<int>> permuteUnique(vector<int>& nums)
    {
        sort(nums.begin(), nums.end());
        dfs(nums);
        return ret;
    }
};

五、电话号码的字母组合


细节:用哈希表存储数字与字符串的映射关系

class Solution
{
    vector<string> ret;
    string path;
    vector<string> hash = {"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public:
    void dfs(string& digits, int pos)
    {
        if(path.size() == digits.size())
        {
            ret.push_back(path);
            return;
        }

        int n = digits[pos] - '0';
        string s = hash[n];
        for(int i=0; i<s.size(); ++i)
        {
            path.push_back(s[i]);
            dfs(digits, pos + 1);
            path.pop_back();
        }
    }

    vector<string> letterCombinations(string& digits)
    {
        if(digits.size() == 0) return ret;
        dfs(digits, 0);
        return ret;
    }
};

六、括号生成



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

        if(left < n)
        {
            path.push_back('(');
            dfs(left + 1, right);
            path.pop_back();
        }

        if(right < left)
        {
            path.push_back(')');
            dfs(left, right + 1);
            path.pop_back();
        }
    }

    vector<string> generateParenthesis(int _n)
    {
        n = _n;
        dfs(0, 0);
        return ret;
    }
};

七、组合


class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
    int n, k;
public:
    void dfs(int pos)
    {
        if(path.size() == k)
        {
            ret.push_back(path);
            return;
        }

        for(int i=pos; i<=n; ++i)
        {
            path.push_back(i);
            dfs(i + 1);
            path.pop_back();
        }
    }

    vector<vector<int>> combine(int _n, int _k)
    {
        n = _n, k = _k;
        dfs(1);
        return ret;
    }
};

八、目标和


class Solution
{
    int ret = 0;
    int t;
public:
    void dfs(vector<int>& nums, int pos, int path)
    {
        if(pos == nums.size())
        {
            if(path == t) ++ret;
            return;
        }

        dfs(nums, pos + 1, path + nums[pos]);
        dfs(nums, pos + 1, path - nums[pos]);
    }

    int findTargetSumWays(vector<int>& nums, int target)
    {
        t = target;
        dfs(nums, 0, 0);
        return ret;
    }
};

九、组合总和


class Solution
{
    vector<vector<int>> ret;
    vector<int> path;
    int t;
public:
    void dfs(vector<int>& candidates, int pos, int sum)
    {
        if(sum >= t)
        {
            if(sum == t) ret.push_back(path);
            return;
        }

        for(int i=pos; i<candidates.size(); ++i)
        {
            path.push_back(candidates[i]);
            dfs(candidates, i, sum + candidates[i]);
            path.pop_back();
        }
    }

    vector<vector<int>> combinationSum(vector<int>& candidates, int target)
    {
        t = target;
        dfs(candidates, 0, 0);
        return ret;
    }
};

十、字母大小写全排列


class Solution
{
    vector<string> ret;
public:
    void dfs(string& s, int pos, string path)
    {
        if(path.size() == s.size())
        {
            ret.push_back(path);
            return;
        }

        if(isalpha(s[pos]))
        {
            dfs(s, pos + 1, path + (char)tolower(s[pos]));
            dfs(s, pos + 1, path + (char)toupper(s[pos]));
        }
        else dfs(s, pos + 1, path + s[pos]);
    }

    vector<string> letterCasePermutation(string& s)
    {
        dfs(s, 0, "");
        return ret;
    }
};

十一、优美的排列


class Solution
{
    int ret = 0;
    bool check[20];
    int n;
public:
    void dfs(int pos, int path)
    {
        if(path == n)
        {
            ++ret;
            return;
        }

        for(int i=1; i<=n; ++i)
        {
            if(!check[i] && (i % pos == 0 || pos % i == 0))
            {
                check[i] = true;
                dfs(pos + 1, path + 1);
                check[i] = false;
            }
        }
    }

    int countArrangement(int _n)
    {
        n = _n;
        dfs(1, 0);
        return ret;
    }
};

总结

  • 决策树
  • 设计代码
    • 全局变量
    • dfs函数
    • 细节问题
      • 回溯
      • 剪枝
      • 递归出口

真诚点赞,手有余香

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Redis-发布与订阅

发布与订阅 什么是发布与订阅 Redis 发布订阅 (pub/sub) 是一种消息通信模式&#xff1a;发送者 (pub) 发送消息&#xff0c;订阅者 (sub) 接收消息。 Redis 客户端可以订阅任意数量的频道。 Redis的发布与订阅 客户端订阅频道 当给这个频道发送消息后&#xff0c;消息就会…

英伟达发布AM-RADIO高效视觉基础模型,推理速度提升6倍,性能超CLIP、DINOv2、SAM

前言 近年来&#xff0c;视觉基础模型 (VFM) 在众多下游任务中取得了巨大成功&#xff0c;例如图像分类、目标检测和图像生成等。然而&#xff0c;现有的 VFM 通常专注于特定领域&#xff0c;例如 CLIP 擅长零样本视觉语言理解&#xff0c;DINOv2 擅长语义分割&#xff0c;SAM…

如何在外网访问内网共享文件?

在日常工作和生活中&#xff0c;我们经常会遇到外网访问内网共享文件的需求。我们可能需要远程访问公司内部的共享文件夹&#xff0c;或者与不同地区的合作伙伴共享文件。由于网络安全的限制&#xff0c;外网访问内网的共享文件并不是一件容易的事情。 为了解决这个问题&#x…

matlab使用教程(70)—修改坐标区属性

1.控制坐标轴长度比率和数据单位长度 您可以控制 x 轴、y 轴和 z 轴的相对长度&#xff08;图框纵横比&#xff09;&#xff0c;也可以控制一个数据单位沿每个轴的相对长度&#xff08;数据纵横比&#xff09;。 1.1图框纵横比 图框纵横比是 x 轴、y 轴和 z 轴的相对长度。默认…

C++ | Leetcode C++题解之第86题分隔链表

题目&#xff1a; 题解&#xff1a; class Solution { public:ListNode* partition(ListNode* head, int x) {ListNode* small new ListNode(0);ListNode* smallHead small;ListNode* large new ListNode(0);ListNode* largeHead large;while (head ! nullptr) {if (head-…

前端小技巧:如何自定义网页的右键菜单(如何禁用网页的右键菜单)

文章目录 📖 介绍 📖🏡 演示环境 🏡📒 右键菜单设置 📒📝 自定义右键菜单实现步骤📝 示例代码📝 涉及的JavaScript语法和参数📝 禁用特定区域的右键菜单⚓️ 相关链接 ⚓️📖 介绍 📖 在网页设计中,一个直观且个性化的右键菜单可以显著提升用户的交互…

WPS表格:对比少于1万的两列数据

当我们需要对于A、B两列乱序的数据&#xff0c;找出A列中某一项B列有没有&#xff0c;或者找出B列中的某一项A列有没有&#xff0c;都可以先将这两列数据放入WPS表格中&#xff1a; 1.选中C列的第一行的单元格&#xff0c;在函数区输入函数 如果我们以A为基准&#xff0c;找A中…

HR4988内置转换器和过流保护的微特步进电机驱动芯片

描述 HR4988是一款内部集成了译码器的微特步进电机驱动器&#xff0c;能使双极步进电机以全、半、1/4、1/8、1/16步进模式工作。步进模式由逻辑输入管脚MSx选择。其输出驱动能力达到32V和2A。 译码器是HR4988易于使用的关键。通过STEP管脚输入一个脉冲就可以使电机完成一次步进…

软件工程期末复习(4)

软件过程 软件过程是为了获得高质量软件所需要完成的一系列任务的框架&#xff0c;它规定了完成各项任务的工作步骤。 ISO 9000对过程的定义: 使用资源将输入转化为输出的活动所构成的系统。 瀑布模型&#xff1a; 瀑布模型的特点&#xff1a; 阶段间具有顺序性和依赖性 必须…

Docker和Kubernetes之间的关系

Docker和Kubernetes在容器化生态系统中各自扮演着不同的角色 它们之间是互补的&#xff0c;而不是替代关系。 Docker是一个开源的容器化平台&#xff0c;它允许开发人员将应用程序及其依赖项打包到一个可移植的容器中&#xff0c;并确保这些容器可以在任何Docker环境中一致地…

Embedding技术学习

可能很多人并没有关注Embedding技术&#xff0c;但实际上它是GPT非常重要的基础&#xff0c;准备的说&#xff0c;它是GPT模型中理解语言/语义的基础。 【解释什么是Embedding】 对于客观世界&#xff0c;人类通过各种文化产品来表达&#xff0c;比如&#xff1a;语言&#x…

GIAT: 蛋白质结构预测的新利器

瑞典Karolinska研究院在瑞典政府赞助下由Ben Murrell等研究团队在AlphaFold 3最新报告后提出这篇论文提出了一种非常有趣和创新的方法来生成蛋白质骨架结构,称为生成式不变角度转换器(GIAT)。与现有的主要基于扩散模型和流匹配的方法不同,GIAT采用了类似于大型语言模型(如GPT)中…

06-Fortran基础--Fortran模块化编程

06-Fortran基础--Fortran模块化编程 1 模块的定义和使用2 接口和模块间通信3 模块化编程的优势&#xff1a;4 模块使用示例5 结语 Fortran的模块化编程是一种组织和管理代码的方法&#xff0c;它包括模块的定义和使用、接口和模块间通信以及模块化编程的优势。 1 模块的定义和…

【35分钟掌握金融风控策略24】定额策略实战

目录 基于客户风险评级的定额策略 确定托底额度和盖帽额度 确定基础额度 基于客户风险评级确定风险系数 计算最终授信额度 确定授信有效期 基于客户风险评级的定额策略 在开发定额策略时&#xff0c;精准确定客户的基础额度是一个关键步骤&#xff0c;通常会基于客户的收…

基于地平线J6E,「吃蟹者」易航智能重塑高速NOA

作者 |张祥威 编辑 |德新 一批基于地平线J6E的智驾方案将要到来&#xff0c;高速NOA领域很快会变天。 易航智能是这批智驾方案公司中的一家。 近日在北京车展&#xff0c;这家公司推出一套基于地平线J6 E的7V1R方案&#xff0c;可以实现城市记忆领航、高速NOA、记忆泊车、L2…

数据结构---经典链表OJ

乐观学习&#xff0c;乐观生活&#xff0c;才能不断前进啊&#xff01;&#xff01;&#xff01; 我的主页&#xff1a;optimistic_chen 我的专栏&#xff1a;c语言 点击主页&#xff1a;optimistic_chen和专栏&#xff1a;c语言&#xff0c; 创作不易&#xff0c;大佬们点赞鼓…

C++八股(面试题、手撕题)自用版

目录 面试题&#xff1a; 1. define inline 在编译的哪个阶段 2. const static 3. 子函数返回结构体有什么问题&#xff0c;返回对象调用了哪些函数 4. volatile关键字 5. 编译器基本原理 6. 预处理、编译、汇编、链接以及他们在操作系统上如何运作的 7. 数组和指针&a…

19、案例实战:上亿请求轻松应对,老年代垃圾回收参数调整技巧大公开

19.1、前文回顾 在上一篇文章中,我们已经向大家介绍了一个日活跃用户百万级别,处理请求量上亿的电商系统案例。我们选择了这个中型电商系统在大促期间的瞬时高峰下单场景,作为我们的JVM优化分析的场景。通过预测,我们得出在大促高峰期,每台机器每秒需要处理300个订单请求…

LINUX 入门 7

LINUX 入门 7 day10 20240506 耗时&#xff1a;59min day11 20240507 耗时&#xff1a;106min 课程链接地址 第7章 http客户端请求 1 http项目介绍与Http协议讲解 先去看一遍教程 扫一遍&#xff0c;不用完全一行行读 ctrlshiftI调出来网页调试台——network——img 过…

PC的体系结构

冯诺依曼体系结构 冯诺依曼体系结构&#xff0c;也称为冯诺依曼架构&#xff0c;是一种计算机架构的设计概念&#xff0c;由20世纪中叶的数学家和物理学家约翰冯诺依曼提出。这种架构的核心特点是将程序指令和数据存储在同一块可读写的存储器中。这样做的优点是简化了计算机的…