【算法专题】穷举vs暴搜vs深搜vs回溯vs剪枝

news2024/9/22 4:10:59

 二叉树剪枝

LCR 047. 二叉树剪枝 - 力扣(LeetCode)

        本题要求我们将全部为0的二叉树去掉,也就是剪枝,当我们举一个具体的例子进行模拟时,会发现,只关注于对其中一个子树的根节点进行剪枝,由于我们只去掉所有节点都是0的子树,所以需要先判断它的左子树是否被去掉,右子树是否被去掉,最后再判断根节点本身的值是否为0,如果这三个条件全部满足,我们需要告诉这个子树的父亲,该子树被去掉了。

        通过上面的分析,不难发现,这个递归函数的传入参数只需要一个根节点的指针,并且该函数需要把剪枝的结果传给父亲,所以递归函数需要一个返回值。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    TreeNode* pruneTree(TreeNode* root) 
    {
        if(root == nullptr) return nullptr;
        root->left = pruneTree(root->left);
        root->right = pruneTree(root->right);
        if(root->left == nullptr && root->right == nullptr && root->val == 0) 
        {
            delete root;
            root = nullptr;
        }
        return root;
    }
};

验证二叉搜索树

98. 验证二叉搜索树 - 力扣(LeetCode)

        关于二叉搜索树,有一个简单的性质就是:二叉搜索树的中序遍历序列是有序的。所以我们可以根据这个性质来验证一个二叉树是否为二叉搜索树,当然我指的并不是创建一个数组进行判断,而是递归实现二叉树的中序遍历,并在这个过程中去判断是否满足性质。

        我们之所以要根据中序遍历序列来判断而不是简单地递归判断左节点小于根节点小于右节点是因为满足这个性质的不一定是二叉搜索树:

        说回正题,那我们要怎么确定二叉树的中序遍历序列呢?实际上我们可以定义一个全局变量prev来充当遍历的“指针”,那么中序遍历过程中,我们只需要满足根节点的值大于prev,就能确保满足二叉搜索树的性质。

        目前的思路已经能够完成这道题目了,但是我们现在就相当于老老实实地把整个二叉树遍历一遍,返回结果。但是只要我们发现左子树或者中间节点不符合二叉搜索树性质,就可以直接返回结果了,因为这注定不可能是二叉搜索树了。而这个操作,其实就是剪枝。

class Solution {
public:
    long prev = LONG_MIN;
    bool isValidBST(TreeNode* root) 
    {
        if(root == nullptr) return true;
        // 判断左子树是否满足二叉搜索树性质
        bool left = isValidBST(root->left);
        if(!left) return false; // 剪枝
        // 判断当前节点是否满足性质
        bool cur = false;
        if(root->val > prev)
            cur = true;
        if(!cur) return false; // 剪枝
        // 更新prev值
        prev = root->val;
        // 判断右子树是否满足二叉搜索树性质
        bool right = isValidBST(root->right);
        return left && right && cur;
    }
};

二叉树的所有路径

257. 二叉树的所有路径 - 力扣(LeetCode)


        依据题意,我们需要把二叉树的所有从根节点到叶子节点的路径找出来,这显然就是个深度优先遍历问题(dfs),因为存在这样的情况:遍历到左边的叶子节点后,得到一个字符串后,进行回溯,再遍历右边的叶子节点,得到另一个字符串。所以我们需要想办法能对字符串进行回溯。

        我们举一两个例子进行模拟就能发现,遍历二叉树的方式应为前序遍历,也就是先将根节点的值加入字符串,然后分别处理左子树和右子树。因为我们需要返回所有得到的字符串,所以可以定义一个全局的字符串数组,又因为我们需要得到从根节点到叶子的路径,所以递归函数还需要传入当前已经遍历的路径。

        至于前面提到的回溯,其实只要递归函数传字符串的值而不是引用就行了,这样的话,遍历完左子树,将左子树的路径加入字符串数组,由于不改变已经遍历的路径,再去遍历右子树,就相当于实现了回溯。

class Solution {
public:
    vector<string> ret;
    void dfs(TreeNode* root, string path)
    {
        if(root->left == nullptr && root->right == nullptr) 
        {
            path += to_string(root->val);
            ret.push_back(path);
            return;
        }
        path += to_string(root->val) + "->";
        if(root->left) dfs(root->left, path);
        if(root->right) dfs(root->right, path);
    }
    vector<string> binaryTreePaths(TreeNode* root) 
    {
        string path;
        dfs(root, path);
        return ret;
    }
};

全排列

46. 全排列 - 力扣(LeetCode)

        对于这种枚举类型的题目,我们首先要做的就是画出决策树,然后根据决策树进行递归代码的编写。题目要求我们返回所有满足条件的数组,我们如果让递归函数来传这些变量,不但存在开销,代码编写也会变得复杂,所以可以直接定义全局变量,省去考虑各种情况的麻烦。

        关于全局变量的定义,首先肯定是一个二维数组ret,存所有排列的可能情况;又因为枚举过程中我们可能需要经常进行回溯,所以把存放排列顺序的数组path也定义为全局变量;最后,因为我们的决策树需要进行剪枝,所以为了方便判断是否剪枝,可以再定义bool类型的数组check,表示当前访问的节点是否已经被遍历。

        递归函数的编写我们只需要考虑当前节点要干什么:遍历check数组判断是否遍历各子树,如果需要遍历,则将子节点值添加到path数组,将当前位置的check设为true,然后递归调用自己,返回后,进行回溯,将子节点从path移除,重新将check设为false.

        最后是递归出口,当path的大小和nums的大小一样时,说明已经是一个完整的序列,可以将path添加到ret后,返回。

class Solution {
public:
    vector<vector<int>> ret;
    vector<int> path;
    bool check[7];
    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;
            }
        }
    }
    vector<vector<int>> permute(vector<int>& nums) 
    {
        dfs(nums);
        return ret;
    }
} ;

子集

78. 子集 - 力扣(LeetCode)

        和上一道题类似,我们依旧是先画出决策树,再根据决策树找出重复的子问题,编写递归函数。在这里,我将画出两种决策树,并分别编写对应的代码。

         首先,第一种决策树的思路是:我们在树的每一层进行判断是否选择某个数组元素,这样一来,当我们遍历完数组时,决策树的叶子节点就是符合条件的数组了。

        在这里,我们同样使用全局变量来进行优化,最后需要返回的二维数组ret和暂时存放结果的数组path都可以定义为全局变量。接着,我们开始考虑dfs函数所需要做的事情,对于决策树的每个节点,dfs要做的事情无非是:存放数组元素的情况,此时需要把当前数组元素存到path,然后递归调用自己,进入决策树的下一层;不存放数组元素的情况,此时不需要把当前数组元素存到path,直接递归调用自己,进入决策树的下一层。于是我们的递归函数除了需要传入数组nums外,还需要传入当前遍历到的nums下标pos的位置。

        最后,如果我们想要正确编写代码,还需要考虑一些细节问题:1.递归出口:很简单,当pos越界时,我们的决策树已经考虑完了所有的情况,这时候就能将path添加到ret中,然后返回。2.是否进行剪枝,显然我们的决策树是不需要剪枝的。3.如何进行回溯,在存放数组的情况下,我们需要把数组元素添加到path中,所以在这种情况dfs完后,我们需要把这个数组元素从path中删除,这种恢复现场的操作,就是回溯。

class Solution {
public:    
    vector<int> path;
    vector<vector<int>> ret;
    vector<vector<int>> subsets(vector<int>& nums) 
    {
        dfs(nums, 0);
        return ret;
    }
    void dfs(vector<int> &nums, int i)
    {
        if(i == nums.size()) --- 当i越界时,直接返回
        {
            ret.push_back(path);
            return ;
        }
        path.push_back(nums[i]);
        dfs(nums, i + 1);
        path.pop_back();
    
        dfs(nums, i + 1);
    }
};

        第二种决策树,我们设计让决策树的每一层从指定的位置开始遍历数组,对每个数组元素的情况分别进行dfs,并且向dfs传入的指定位置就是数组元素的下标,这样一来就保证了我们能够不重不漏地找出所有子集。在这种情况下,我们在决策树的每个节点都将path添加到ret中。

class Solution {
public:
    // 解法二:决策树每个节点都是返回值
    vector<vector<int>> ret;
    vector<int> path;
    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();
        }
    }

};

括号生成

22. 括号生成 - 力扣(LeetCode)             

              本题的决策树非常的简单,我们只需要在每层判断选择左括号还是右括号即可,但是显然这棵决策树是有不少不符合条件的树枝的,所以需要进行剪枝。

        为了对决策树进行剪枝,我们首先要先明确一下有效的括号组合是什么? 1.左括号的数量等于右括号的数量。2.对从头开始的子串而言,左括号的数目大于等于右括号的数目。所以我们就根据这两点进行剪枝。

        和前面的题目类似,我们使用全局变量来简化代码,首先是一个存储单次dfs结果的字符串path,还有存放结果的字符串数组ret,除此之外,为了根据左括号和右括号的数量进行剪枝,我们还需要用left和right分别表示左右括号的数量。

        接下来,让我们思考一下dfs函数在某一个节点中具体需要完成什么任务。dfs需要对左括号和右括号的情况进行深度优先遍历,并且在左括号处理完成后,需要恢复现场,然后去处理右括号。

        最后是递归出口,当我们的右括号等于n时,根据有效括号的性质,左括号必定也等于n,此时我们就找到了一个有效括号组合,将path存入ret后,返回即可。

                                                                                              

class Solution {
public:
    int left, right, n;
    string path;
    vector<string> ret;
    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 < n && right < left)
        {
            path.push_back(')'), right++;
            dfs();
            path.pop_back(), right--;
        }
    }
};

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

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

相关文章

企业竞争文化数据,词频分析(2007-2022年)

企业竞争文化的核心价值观包括&#xff1a; 追求卓越&#xff1a;鼓励员工不断超越自我&#xff0c;提升个人和团队的绩效。领导力&#xff1a;强调领导者在塑造竞争文化中的重要作用&#xff0c;引领团队向更高目标前进。创新思维&#xff1a;倡导员工面对挑战时采取创新方法…

25届计算机专业选题推荐-基于微信小程序的校园快递驿站代收管理系统

&#x1f496;&#x1f525;作者主页&#xff1a;毕设木哥 精彩专栏推荐订阅&#xff1a;在 下方专栏&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; 实战项目 文章目录 实战项目 一、基于微信小程序的校园快递驿…

Golang | Leetcode Golang题解之第406题根据身高重建队列

题目&#xff1a; 题解&#xff1a; func reconstructQueue(people [][]int) (ans [][]int) {sort.Slice(people, func(i, j int) bool {a, b : people[i], people[j]return a[0] > b[0] || a[0] b[0] && a[1] < b[1]})for _, person : range people {idx : pe…

【SQL Server】清除日志文件ERRORLOG、tempdb.mdf

数据库再使用一段时间后&#xff0c;日志文件会增大&#xff0c;特别是在磁盘容量不足的情况下&#xff0c;更是需要缩减&#xff0c;以下为缩减方法&#xff1a; 如果可以停止 SQL Server 服务&#xff0c;那么可以采取更直接的方式来缩减 ERRORLOG 和 tempdb.mdf 文件的大小…

机器学习课程学习周报十二

机器学习课程学习周报十二 文章目录 机器学习课程学习周报十二摘要Abstract一、机器学习部分1.1 fGAN: General Framework of GAN1.2 CycleGAN1.3 Auto-Encoder1.4 概率论复习&#xff08;一&#xff09; 总结 摘要 本周的学习内容涵盖了fGAN框架、CycleGAN、自编码器以及概率…

【逐行注释】自适应Q和R的AUKF(自适应无迹卡尔曼滤波),附下载链接

文章目录 自适应Q的KF逐行注释的说明运行结果部分代码各模块解释 自适应Q的KF 自适应无迹卡尔曼滤波&#xff08;Adaptive Unscented Kalman Filter&#xff0c;AUKF&#xff09;是一种用于状态估计的滤波算法。它是基于无迹卡尔曼滤波&#xff08;Unscented Kalman Filter&am…

通义灵码在Visual Studio上

通义灵码在Visual Studio上不好用&#xff0c;有时候会出现重影&#xff0c;不如原生的自动补全好用&#xff0c;原生的毕竟的根据语法来给出提示的。

MySQL练手题--体育馆的人流量(困难)

一、准备工作 Create table If Not Exists Stadium (id int, visit_date DATE NULL, people int); Truncate table Stadium; insert into Stadium (id, visit_date, people) values (1, 2017-01-01, 10); insert into Stadium (id, visit_date, people) values (2, 2017-01-02…

Java 每日一刊(第8期):流程控制

“计算机程序本质上是艺术的一种表现形式。” 前言 这里是分享 Java 相关内容的专刊&#xff0c;每日一更。 本期将为大家带来以下内容&#xff1a; 条件控制语句循环控制语句跳转控制语句 条件控制语句 条件控制语句用于 根据条件判断执行不同的代码块&#xff0c;是编程…

COMP 6714-Info Retrieval and Web Search笔记week1

哭了哭了&#xff0c;这周唯一能听懂的就这门 目录 IR&#xff08;Information Retrieval)是什么&#xff1f;IR的基本假设Unstructured (text) vs. structuredDocuments vs. Database Records比较文本&#xff08;Comparing Text&#xff09;IR的范围(Dimensions of IR)IR的任…

【目标检测数据集】锯子数据集1107张VOC+YOLO格式

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;1107 标注数量(xml文件个数)&#xff1a;1107 标注数量(txt文件个数)&#xff1a;1107 标注…

如何使用myabtis log plugin插件展示出数据库查询语句

1、安装myabtis log plugin插件 直接插件市场搜该插件进行安装就行&#xff0c;安装完成后&#xff0c;会有如下图标 2、需要集成log4j springboot版本需要集成log4j&#xff0c;集成遇到的问题可以参考我之前文章 3、配置log4j.xml文件&#xff0c;添加mapper文件的打印 &l…

软考高级:存储系统 DAS、NAS、SAN 区别 - AI 解读

DAS&#xff08;直接附加存储&#xff09;、NAS&#xff08;网络附加存储&#xff09;和SAN&#xff08;存储区域网络&#xff09;是三种常见的数据存储架构&#xff0c;主要用于不同场景下的数据存储和管理。我们可以从它们的架构、用途以及优缺点来理解。 生活化例子 想象一…

标准库标头 <bit>(C++20)学习

<bit>头文件是数值库的一部分。定义用于访问、操作和处理各个位和位序列的函数。例如&#xff0c;有函数可以旋转位、查找连续集或已清除位的数量、查看某个数是否为 2 的整数幂、查找表示数字的最小位数等。 类型 endian (C20) 指示标量类型的端序 (枚举) 函数 bit_ca…

阿里云 Quick BI使用介绍

Quick BI使用介绍 文章目录 阿里云 Quick BI使用介绍1. 创建自己的quick bi服务器2. 新建数据源3. 上传文件和 使用4. 开始分析 -选仪表盘5. 提供的图表6. 一个图表的设置使用小结 阿里云 Quick BI使用介绍 Quick BI是一款全场景数据消费式的BI平台&#xff0c;秉承全场景消费…

文学智能体——摄影皮卡丘

前言 今天尝试进行智能体创建&#xff0c;我想创建什么呢&#xff0c;旅游的话&#xff0c;除了美食那就是摄影啦&#xff0c;那我就创建个皮卡丘吧&#xff0c;就决定是你啦&#xff0c;摄影皮卡丘&#xff01; 一、创建智能体 那怎么创建一个皮卡丘呢&#xff0c;那就使用…

cc2530按键中断实现控制LED

1开启中断的步骤 1-1使能端口组的中断 IEN1 IEN2 实例 IEN2 | 0x10 //使能P1口中断 1-2 端口中断屏蔽 P0IEN和P1IEN P2IEN 实例 P1IEN |0x10&#xff1b; //使能P1_2口中断 1-3设置触发方式 PICTL 实例 PICTL |0X02 ;//P1_3到P1_2口下降沿触发 1-4设置中断优先级…

解决:Vue 中 debugger 不生效

目录 1&#xff0c;问题2&#xff0c;解决2.1&#xff0c;修改 webpack 配置2.2&#xff0c;修改浏览器设置 1&#xff0c;问题 在 Vue 项目中&#xff0c;可以使用 debugger 在浏览器中开启调试。但有时却不生效。 2&#xff0c;解决 2.1&#xff0c;修改 webpack 配置 通…

【webpack4系列】webpack构建速度和体积优化策略(五)

文章目录 速度分析&#xff1a;使用 speed-measure-webpack-plugin体积分析&#xff1a;使用webpack-bundle-analyzer使用高版本的 webpack 和 Node.js多进程/多实例构建资源并行解析可选方案使用 HappyPack 解析资源使用 thread-loader 解析资源 多进程并行压缩代码方法一&…

掌握远程管理的艺术:揭秘Python的pywinrm库

文章目录 &#x1f525; 掌握远程管理的艺术&#xff1a;揭秘Python的pywinrm库 &#x1f525;背景&#xff1a;为何选择pywinrm&#xff1f;pywinrm库简介安装pywinrm库简单库函数使用方法场景应用常见问题与解决方案总结 &#x1f525; 掌握远程管理的艺术&#xff1a;揭秘Py…