【算法】{画决策树 + dfs + 递归 + 回溯 + 剪枝} 解决排列、子集问题(C++)

news2024/11/15 8:33:17

文章目录

  • 1. 前言
  • 2. 算法例题 理解思路、代码
    • 46.全排列
    • 78.子集
  • 3. 算法题练习
    • 1863.找出所有子集的异或总和再求和
    • 47.全排列II
    • 17.电话号码的字母组合

1. 前言

  1. dfs问题 我们已经学过,对于排列、子集类的问题,一般可以想到暴力枚举,但此类问题用暴力解法 一般都会超时,时间开销过大。
  2. 对于该种问题,重点在于尽可能详细的 画决策树,随后根据决策树分析 题目所涉及的 剪枝、回溯、递归等细节问题。
  3. 根据决策树的画法不同,题目会有不同的解法,只要保证决策树没有问题,保证细节问题下 代码一定可以编写出来。

2. 算法例题 理解思路、代码

46.全排列

在这里插入图片描述

思路

  • 思路:求出数组中元素的所有排列顺序,并用数组输出。

  • 解法一 暴力枚举

    • 用n层for循环,每层循环依次固定一个数
    • 超时,时间开销太大,n个元素就是O(n ^ n)
  • 解法二 根据决策树 进行递归
    在这里插入图片描述

    • 根据上图,我们需要创建下面三个全局变量.
      在这里插入图片描述
    • 结束条件:当我们遍历到叶子节点时(即path.size() == nums.size()),将path加入到ret中,并向上返回。
    • 回溯:对当前元素dfs后,进行回溯,回溯即将之前加入到path 的元素删除,并将used重新置为false。

代码

class Solution {
public:
    vector<vector<int>> ret; // 用于存储最终结果
    bool used[7]; // 用于记录某个下标的元素是否在序列中
    vector<int> path; // 用于记录某个下标的元素是否已经加入到序列中
    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;
        }

        // 遍历数组,对每一位都进行dfs && 排列
        for(int i = 0; i < nums.size(); ++i)
        {
            if(used[i] == false) // 如果该位置未加入到当前序列中
            {
                path.push_back(nums[i]);
                used[i] = true;
                dfs(nums);
                // dfs向上返回回来 -> 回溯
                path.pop_back();
                used[i] = false;
            }
        }
    }
};

78.子集

在这里插入图片描述

  • 题目要求我们将数组的所有子集统计,并以数组形式返回(空集就是空数组)

解法一

  • 解法 根据 选与不选 画决策树
    在这里插入图片描述

    • 根据上图决策树,我们通过对一个元素的选择与否划分树,而当到达叶子节点的时候(i == nums.size())向上返回即可。
    • 函数头:首先需要的参数是数组本身,其次我们通过变量i来标记当前选择的元素所在层数,则 void dfs(vector<int>& nums, int i)
    • 函数体:分别写出选择与不选择该元素时的代码即可
    • 结束条件:如前面所说,当 到叶子节点时返回。

代码

class Solution {
public:
    vector<vector<int>> ret;
    vector<int> path;
    vector<vector<int>> subsets(vector<int>& nums) {
        int i = 0;
        dfs(nums, i);
        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();
    }
};

解法二

在这里插入图片描述

  • 解法 根据子集包含的元素个数 画 决策树
  • 如上图所示,以此法画的决策树,每个节点的值都是有效值
    • 函数头:第一个参数是数组本身,另外需要给出当前遍历到nums的第几个元素。void dfs(vector<int>& nums, int pos)
    • 函数体
      1. 在函数开始时先将当前子集加入到ret中
      2. 利用for循环,每次从pos开始遍历数组:每次将一个元素作为子集第一位的所有子集检索完毕后,再以下一个元素作为子集第一位,可以防止重复子集
        • for循环中每次将当前元素加入到path中,dfs下一位,最后回溯

代码

class Solution {
public:
    vector<vector<int>> ret;
    vector<int> path;
    vector<vector<int>> subsets(vector<int>& nums) {
        int pos = 0;
        dfs(nums, pos);
        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.找出所有子集的异或总和再求和

在这里插入图片描述

思路

  • 题目分析:题目要求求出数组中所有子集的异或和的总和,我们只需要根据上图求子集的思路,在统计子集时,直接用变量计算异或值即可
    • 解法 dfs + 决策树
      • 这道题的决策树与上题一致,只需要在执行方面进行更改。
      • 回溯:由于一个元素异或一个数两次,相当于没有异或,所以对于回溯操作,我们只需要再次进行异或即可。

代码

class Solution {
public:
    int ret = 0; // 最终结果
    int xorSum = 0; // 记录一个子集的异或和
    int subsetXORSum(vector<int>& nums) {
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int>& nums, int pos)
    {
        ret += xorSum;

        for(int i = pos; i < nums.size(); ++i)
        {
            xorSum ^= nums[i];
            dfs(nums, i + 1);
            xorSum ^= nums[i]; // 回溯现场 / 异或同一个数两次相当于无异或
        }
    }
};

47.全排列II

在这里插入图片描述

思路

  • 题目分析:
    在这里插入图片描述

  • 根据上图,制定决策树
    在这里插入图片描述

  • 下面是对于上面决策树的解释,以及根据该决策树,我们如何设计代码
    在这里插入图片描述

  • 我们对上面探讨的两种解法进行解释:
    在这里插入图片描述

    • 如图所示,如果用文字解释,对于不合法路径:
      • 当 【当前元素A已经使用了 && 该分支下已有与当前元素值相同的B && B已经在序列中】时不合法。
  • 编写代码方面,本道题与前面的题非常类似,主要在于主逻辑的差别:

代码

class Solution {
public:
    vector<vector<int>> ret;
    vector<int> path;
    bool used[9];

    vector<vector<int>> permuteUnique(vector<int>& nums) {
        sort(nums.begin(), nums.end()); // 先排序数组
        dfs(nums, 0);
        return ret;
    }

    void dfs(vector<int> nums, int pos)
    {
        if(path.size() == nums.size())
        {
            ret.push_back(path);
            return;
        }

        for(int i = 0; i < nums.size(); ++i)
        {
            // 剪枝 - 考虑合法路径
            if(used[i] == false && (i == 0 || nums[i] != nums[i - 1] || used[i - 1] == true))
	        {
                path.push_back(nums[i]);
                used[i] = true;
                dfs(nums, pos + 1);
                path.pop_back();
                used[i] =  false;
            }
        }
    }
};

17.电话号码的字母组合

思路

在这里插入图片描述

  • 解法 dfs + 决策树
    • 决策树:如下图所示

在这里插入图片描述

  • 本体决策树画出来后,递归回溯等部分相对于前面简单一些
    • 细节问题
      • 我们需要由数字与字符串一一对应来进行号码的模拟,这里可以用哈希表 ——> 优化:数组作为哈希表hash
      • 主逻辑:关于for循环,我们需要从digits中依次提取数字字符,并找到相对应的字符串,即hash[digits[pos] - '0']

代码

class Solution {
public:
    // 数组作哈希,下标对应字符串
    string hash[10] = {"", "", "abc", "def", "ghi", "jkl", "mno", "pqrs", "tuv", "wxyz"};
    string path;
    vector<string> ret;
    vector<string> letterCombinations(string digits) {
        // 特殊情况
        if(digits.size() == 0) return ret;

        dfs(digits, 0);
        return ret;
    }

    void dfs(string& digits, int pos) {
        if(path.size() == digits.size())
        {
            ret.push_back(path);
            return;
        }

        for(char ch : hash[digits[pos] - '0']) // 提取数字字符
        {
            path.push_back(ch);
            dfs(digits, pos + 1);
            path.pop_back();
        }
    }
};

有待更新… …

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

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

相关文章

基于Chrome插件的Chatgpt对话无损导出markdown格式(Typora完美显示)

刚刚提交插件到Chrome插件商店正在审核&#xff0c;想尝试的可以先使用&#xff1a; https://github.com/thisisbaiy/ChatGPT-To-Markdown-google-plugin/tree/main 我将源代码上传至了GitHub&#xff0c;欢迎star, IssueGoogle插件名称为&#xff1a;ChatGPT to MarkDown plus…

海外云手机——平台引流的重要媒介

随着互联网的飞速发展&#xff0c;跨境电商、短视频引流以及游戏行业等领域正经历着迅猛的更新换代。在这个信息爆炸的时代&#xff0c;流量成为至关重要的资源&#xff0c;而其中引流环节更是关乎业务成功的关键。海外云手机崭露头角&#xff0c;成为这一传播过程中的重要媒介…

【保姆级教程|YOLOv8改进】【7】多尺度空洞注意力(MSDA),DilateFormer实现暴力涨点

《博主简介》 小伙伴们好&#xff0c;我是阿旭。专注于人工智能、AIGC、python、计算机视觉相关分享研究。 ✌更多学习资源&#xff0c;可关注公-仲-hao:【阿旭算法与机器学习】&#xff0c;共同学习交流~ &#x1f44d;感谢小伙伴们点赞、关注&#xff01; 《------往期经典推…

Ribbon全方位解析:构建弹性的Java微服务

第1章 引言 大家好,我是小黑,咱们今天聊聊Ribbon,这货是个客户端负载均衡工具,用在Spring Cloud里面能让咱们的服务调用更加灵活和健壮。负载均衡,听起来挺高大上的,其实就是把外界的请求平摊到多个服务器上,避免某个服务器压力太大,其他的却在那儿闲着。 Ribbon的牛…

Springboot整合JUnit5框架

目录 第一章、在pom文件中导入依赖第二章、新建测试类第三章、新建测试方法 友情提醒: 先看文章目录&#xff0c;大致了解文章知识点结构&#xff0c;点击文章目录可直接跳转到文章指定位置。 第一章、在pom文件中导入依赖 SpringBoot2.2x之后的版本中spring-boot-starter-te…

Python(21)正则表达式中的“元字符”

大家好&#xff01;我是码银&#x1f970; 欢迎关注&#x1f970;&#xff1a; CSDN&#xff1a;码银 公众号&#xff1a;码银学编程 获取资源&#xff1a;公众号回复“python资料” 在本篇文章中介绍的是正则表达式中一部分具有特殊意义的专用字符&#xff0c;也叫做“元…

基于51 单片机的交通灯系统 源码+仿真+ppt

主要内容&#xff1a; 1&#xff09;南北方向的绿灯、东西方向的红灯同时亮40秒。 2&#xff09;南北方向的绿灯灭、黄灯亮5秒&#xff0c;同时东西方向的红灯继续亮。 3&#xff09;南北方向的黄灯灭、左转绿灯亮&#xff0c;持续20秒&#xff0c;同时东西方向的红灯继续…

rust语言tokio库底层原理解析

目录 1 rust版本及tokio版本说明1 tokio简介2 tokio::main2.1 tokio::main使用多线程模式2.2 tokio::main使用单线程模式 3 builder.build()函数3.1 build_threaded_runtime()函数新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图…

前端JavaScript篇之对执行上下文的理解

目录 对执行上下文的理解创建执行上下文 对执行上下文的理解 当我们在执行JavaScript代码时&#xff0c;JavaScript引擎会创建并维护一个执行上下文栈来管理执行上下文。执行上下文有三种类型&#xff1a;全局执行上下文、函数执行上下文和eval函数执行上下文。 在写代码的时…

第十三、十四个知识点:用javascript获取表单的内容并加密

我们先来写一段代码&#xff1a; <body><form action"#" method"post">//写一个表单<span>用户名&#xff1a;</span><input type"text" id"username" name"username"><span>密码&a…

BGP 双归不同运营商并且客户之间互为主备的部署实验

一、拓朴&#xff1a; 要求&#xff1a; 1、双方 ISP 均不得将客户 AS 做为穿越 AS 2、对于客户业务的出流量&#xff1a;客户 AS100 和 200 访问 ISP 时&#xff0c;AS100优选从 Line-1 线路&#xff0c;AS200 优选从 Line-2 访问&#xff0c;但当 Line-1 和 …

Springboot+vue的社区智慧养老监护管理平台设计与实现(有报告),Javaee项目,springboot vue前后端分离项目

演示视频&#xff1a; Springbootvue的社区智慧养老监护管理平台设计与实现&#xff08;有报告&#xff09;&#xff0c;Javaee项目&#xff0c;springboot vue前后端分离项目 项目介绍&#xff1a; 本文设计了一个基于Springbootvue的前后端分离的社区智慧养老监护管理平台设…

springboot项目启动报错:dynamic-datasource can not find primary datasource

项目启动报错信息 Caused by: com.baomidou.dynamic.datasource.exception.CannotFindDataSourceException: dynamic-datasource can not find primary datasourceat com.baomidou.dynamic.datasource.DynamicRoutingDataSource.determinePrimaryDataSource(DynamicRoutingDat…

Prime(VulnHub)

Prime 文章目录 Prime1、nmap2、web渗透随便看看首页隐写查看目录爆破gobusterferoxbusterdirsearchdirb whatwebsearchsploit WordPress 5.2.2/dev/secret.txtFuzz_For_Webwfuzzimage.phpindex.php location.txtsecrettier360文件包含漏洞包含出password.txt尝试ssh登入尝试登…

AD9361多片同步设计方法

本文基于ZC706FMCOMMS5的平台&#xff0c;介绍了多片AD9361同步的方法。并将该设计移植到自行设计的ZYNQ70354片AD9361(实现8路同步收发)的电路板上。本设计采用纯逻辑的方式&#xff0c;仅使用了ZYNQ芯片的PL部分。 9361多芯片同步主要包括基带同步和射频同步两大块任务。其中…

idea自带的HttpClient使用

1. 全局变量配置 {"local":{"baseUrl": "http://localhost:9001/"},"test": {"baseUrl": "http://localhost:9002/"} }2. 登录并将结果设置到全局变量 PostMapping("/login")public JSONObject login(H…

Mac电脑到手后的配置

一、Homebrew 1、Homebrew安装 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)" 桌面的Old_Homebrew文件夹&#xff0c;没有你需要的可以删除。 2、Homebrew卸载 /bin/zsh -c "$(curl -fsSL https://gitee.com/c…

2023年ABC123公众号年刊下载(PDF电子书)

Part1 前言 大家好&#xff0c;我是ABC_123。2023年公众号正式更名为"希潭实验室"。除了分享日常红队攻防、渗透测试技术文章之外&#xff0c;重点加强了APT案例分析方面的内容。公众号关注度得到进一步提升&#xff0c;关注人数已达到3万5千人。原计划在2023年编写…

【FPGA开发】Modelsim和Vivado的使用

本篇文章包含的内容 一、FPGA工程文件结构二、Modelsim的使用三、Vivado的使用3.1 建立工程3.2 分析 RTL ANALYSIS3.2.1 .xdc约束&#xff08;Constraints&#xff09;文件的产生 3.3 综合 SYNTHESIS3.4 执行 IMPLEMENTATION3.5 烧录程序3.6 程序固化3.6.1 SPI约束3.6.2 .bin文…

【GAMES101】Lecture 19 透镜

目录 理想的薄透镜 模糊 利用透镜模型做光线追踪 景深&#xff08;Depth of Field&#xff09; 理想的薄透镜 在实际的相机中都是用的一组透镜来作为这个镜头 这个因为真实的棱镜无法将光线真正聚焦到一个点上&#xff0c;它只能聚在一堆上 所以方便研究提出了一种理想化的…