Day20.一刷数据结构算法(C语言版) 669修剪二叉搜索树;108将有序数组转换为二叉搜索树;538把二叉搜索树转换为累加树

一、669修剪二叉搜索树

        这道题目比较难,比添加增加和删除节点难的多,建议先看视频理解。

        题目链接:修剪二叉搜索树

        文章讲解: 代码随想录

        视频讲解: 你修剪的方式不对,我来给你纠正一下!| 修剪二叉搜索树

1.思路分析

        直接想法就是:递归处理,然后遇到 root == NULL || root->val < low || root->val > high 的时候直接return NULL,一波修改,赶紧利落。 

        然而[1, 3]区间在二叉搜索树的中可不是单纯的节点3和左孩子节点0就决定的,还要考虑节点0的右子树

        我们在重新关注一下第二个示例,如图: 

        所以上面的想法是不可行的! 

        但其实也不用像重构那么复杂

        在上图中我们发现节点0并不符合区间要求,那么将节点0的右孩子 节点2 直接赋给节点3的左孩子就可以了(就是把节点0从二叉树中移除),如图:

        递归三部曲:

        1)确定递归函数的参数以及返回值

        因为是要遍历整棵树做修改,其实不需要返回值也可以,我们也可以完成修剪(其实就是从二叉树中移除节点)的操作。

        但是有返回值更方便,可以通过递归函数的返回值来移除节点。

        这样的做法在搜索树中的插入操作和搜索树中的删除操作中大家已经了解过了。

struct TreeNode* trimBST(struct TreeNode* root, int low, int high)

        2)确定终止条件

 if (root == NULL) return NULL;

        3)确定单层递归逻辑

        如果root(当前节点)的元素小于low的数值,那么应该递归右子树,并返回右子树符合条件的头结点。

if (root->val < low) {
    struct TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
    return right;
}
//而对于多余的节点,上面的代码相当于把节点0的右孩子(节点2)返回给上一层。

        如果root(当前节点)的元素大于high的,那么应该递归左子树,并返回左子树符合条件的头结点。 

if (root->val > high) {
    struct TreeNode* left = trimBST(root->left, low, high); // 寻找符合区间[low, high]的节点
    return left;
}

        接下来要将下一层处理完左子树的结果赋给root->left,处理完右子树的结果赋给root->right。

最后返回root节点。代码如下:

root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子
//上面的代码相当于用节点3的左孩子把下一层返回的节点0的右孩子(节点2) 接住。

root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子
return root;

        而此时节点3的左孩子就变成了节点2,将节点0从二叉树中移除了。 

 2.代码详解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* trimBST(struct TreeNode* root, int low, int high) {
        if (root == NULL) return NULL;
        if (root->val < low) {
            struct TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
            return right;
        }
        if (root->val > high) {
            struct TreeNode* left = trimBST(root->left, low, high); // 寻找符合区间[low, high]的节点
            return left;
        }
        root->left = trimBST(root->left, low, high); // root->left接入符合条件的左孩子
        root->right = trimBST(root->right, low, high); // root->right接入符合条件的右孩子
        return root;
    }

 二、108将有序数组转换为二叉搜索树

        这道题就简单一些,可以尝试先自己做做。

        题目链接:将有序数组转换为二叉搜索树

        文章链接:代码随想录

        视频讲解:构造平衡二叉搜索树!| LeetCode:108.将有序数组转换为二叉搜索树

 1.思路分析

        题目中说要转换为一棵高度平衡二叉搜索树。为什么强调要平衡呢?

        因为只要给我们一个有序数组,如果不强调平衡,都可以以线性结构来构造二叉搜索树。

        例如 有序数组[-10,-3,0,5,9] 就可以构造成这样的二叉搜索树,如图:

        上图中,是符合二叉搜索树的特性吧,但如果要这么做的话,是不是本题意义就不大了,所以才强调是平衡二叉搜索树。 

        其实数组构造二叉树,构成平衡树是自然而然的事情,因为大家默认都是从数组中间位置取值作为节点元素,一般不会随机取。

        至于如何将数组构建成二叉树,本质就是寻找分割点,分割点作为当前节点,然后递归左区间和右区间

        二叉搜索树的分割点就是数组中间位置的节点。 

        那么为问题来了,如果数组长度为偶数,中间节点有两个,取哪一个?

        取哪一个都可以,只不过构成了不同的平衡二叉搜索树。

        如果要分割的数组长度为偶数的时候,中间元素为两个,是取左边元素 就是树1,取右边元素就是树2。

        这也是题目中强调答案不是唯一的原因。 理解这一点,这道题目算是理解到位了。        

        递归三部曲:

        1)确定递归函数返回值及其参数

        本题要构造二叉树,依然用递归函数的返回值来构造中节点的左右孩子。

        再来看参数,首先是传入数组,然后就是左下标left和右下标right,我们在构造二叉树的时候尽量不要重新定义左右区间数组,而是用下标来操作原数组。

struct TreeNode* traversal(int* nums, int left, int right)

        这里注意,我这里定义的是左闭右闭区间,在不断分割的过程中,也会坚持左闭右闭的区间,这又涉及到我们讲过的循环不变量。 

        2)确定递归终止条件

        这里定义的是左闭右闭的区间,所以当区间 left > right的时候,就是空节点了。 

if (left > right) return NULL;

        3)确定单层递归逻辑

        首先取数组中间元素的位置,不难写出int mid =(right +left) / 2;

        这么写其实有一个问题,就是数值越界,例如left和right都是最大int,这么操作就越界了,在二分法中尤其需要注意!

        所以可以这么写:int mid = left + ((right - left) / 2);

        但本题leetcode的测试数据并不会越界,所以怎么写都可以。但需要有这个意识!

        取了中间位置,就开始以中间位置的元素构造节点:struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));

        接着划分区间,root的左孩子接住下一层左区间的构造节点,右孩子接住下一层右区间构造的节点。

        最后返回root节点,单层递归整体代码如下:

int mid = left + ((right - left) / 2);
struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
root->val = nums[mid];
root->left = traversal(nums, left, mid - 1);
root->right = traversal(nums, mid + 1, right);
return root;

        这里int mid = left + ((right - left) / 2);的写法相当于是如果数组长度为偶数,中间位置有两个元素,取靠左边的。 

 2.代码详解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
struct TreeNode* traversal(int* nums, int left, int right) {
    if (left > right) 
        return NULL;
    int mid = left + ((right - left) / 2);
    struct TreeNode* root = (struct TreeNode*)malloc(sizeof(struct TreeNode));
    root->val = nums[mid];
    root->left = traversal(nums, left, mid - 1);
    root->right = traversal(nums, mid + 1, right);
    return root;
}

struct TreeNode* sortedArrayToBST(int* nums, int numsSize) {
    struct TreeNode* root = traversal(nums, 0, numsSize - 1);
    return root;
}

 三、538把二叉搜索树转换为累加树

        本题也不难,在 求二叉搜索树的最小绝对差 和 众数 那两道题目 都讲过了 双指针法,思路是一样的。

        题目链接:把二叉树搜索树转换为累加树

        文章链接:代码随想录

        视频讲解:普大喜奔!二叉树章节已全部更完啦!| 把二叉搜索树转换为累加树

1.思路分析

        有序的元素如何求累加呢?

        其实这就是一棵树,大家可能看起来有点别扭,换一个角度来看,这就是一个有序数组[2, 5, 13],求从后到前的累加数组,也就是[20, 18, 13],是不是感觉这就简单了。

        为什么变成数组就是感觉简单了呢?

        因为数组大家都知道怎么遍历啊,从后向前,挨个累加就完事了,这换成了二叉搜索树,看起来就别扭了一些是不是。

        那么知道如何遍历这个二叉树,也就迎刃而解了,从树中可以看出累加的顺序是右中左,所以我们需要反中序遍历这个二叉树,然后顺序累加就可以了

        递归顺序如图所示:

        本题依然需要一个pre指针记录当前遍历节点cur的前一个节点,这样才方便做累加。

        pre指针的使用技巧,我们在二叉树:搜索树的最小绝对差 和二叉树:我的众数是多少?都提到了,这是常用的操作手段。

        递归三部曲:

        1)递归函数参数以及返回值

        这里很明确了,不需要递归函数的返回值做什么操作了,要遍历整棵树。

        同时需要定义一个全局变量pre,用来保存cur节点的前一个节点的数值,定义为int型就可以了。

int pre;
void traversal(struct TreeNode* node) 

        2)确定终止条件

if(!node) return ;

        3)确定单层递归逻辑

        注意要右中左来遍历二叉树, 中节点的处理逻辑就是让cur的数值加上前一个节点的数值。 

traversal(node->right);
node->val = node->val + pre;
pre = node->val;
traversal(node->left);

 2.代码详解

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     struct TreeNode *left;
 *     struct TreeNode *right;
 * };
 */
int pre;
void traversal(struct TreeNode* node) {
    if(!node)
        return ;
    traversal(node->right);
    node->val = node->val + pre;
    pre = node->val;
    traversal(node->left);
}

struct TreeNode* convertBST(struct TreeNode* root){
    pre = 0;
    traversal(root);
    return root;
}

        二叉树部分结束。 

        如果你有问题或者有其他想法,欢迎评论区留言,大家可以一起探讨。

 

 

 

 

 

 

 

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

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

相关文章

AI预测体彩排列3第2套算法实战化测试第6弹2024年4月28日第6次测试

今天继续进行新算法的测试&#xff0c;今天是第6次测试。好了&#xff0c;废话不多说了&#xff0c;直接上图上结果。 2024年4月28日体彩排3预测结果 6码定位方案如下&#xff1a; 百位&#xff1a;3、2、1、0、5、6、7 十位&#xff1a;3、5、6、2、1、0 个位&#xff1a;3、4…

iOS - 多线程-atomic

文章目录 iOS - 多线程-atomic1. 源码分析1.1 get方法1.2 set方法 2. 一般不使用atomic的原因 iOS - 多线程-atomic atomic用于保证属性setter、getter的原子性操作&#xff0c;相当于在getter和setter内部加了线程同步的锁可以参考源码objc4的objc-accessors.mm它并不能保证使…

【无监督+自然语言】 GPT,BERT, GPT-2,GPT-3 生成式预训练模型方法概述 (Generative Pre-Traning)

主要参考 【GPT&#xff0c;GPT-2&#xff0c;GPT-3 论文精读【李沐论文精读】-2022.03.04】 https://www.bilibili.com/video/BV1AF411b7xQ/ 大语言模型综述&#xff1a; https://blog.csdn.net/imwaters/article/details/137019747 GPT与chatgpt的关系 图源&#xff1a;L…

【Verilog-语法】 条件编译 `ifdef/`ifndef

一、前言 在Verilog项目开发过程中某功能是&#xff0c;一部分代码可能有时候用&#xff0c;有时候不用&#xff0c;为了避免全部编译占用资源&#xff0c;可以使用条件编译语句&#xff1b;尤其在大型项目中还可以节约大量的时间。 二、语法 语法书写格式&#xff1a; &am…

嵌入式Linux学习——Linux常用命令(下)

压缩/解压缩命令 gzip gzip 的常用选项&#xff1a; -l(list) 列出压缩文件的内容。-k(keep) 在压缩或解压时&#xff0c;保留输入文件。-d(decompress) 将压缩文件进行解压缩。 注意&#xff1a; 如果 gzip 不加任何选项&#xff0c;此时为压缩。 压缩完该文件会生成后缀…

web自动化系列-selenium的基本方法介绍

web自动化 &#xff0c;一个老生常谈的话题 &#xff0c;很多人的自动化之路就是从它开始 。它学起来简单 &#xff0c;但做起来又比较难以驾驭 &#xff1b;它的执行效率慢 、但又是最接近于用户的操作场景 &#xff1b; 1.web自动化中的三大亮点技术 我们先聊聊 &#xff0…

hive安装

文章目录 1、下载hive2、安装hadoop&#xff08;略&#xff09;3、安装mysql&#xff08;略&#xff09;4、安装 1、下载hive https://dlcdn.apache.org/hive/hive-3.1.3/ 2、安装hadoop&#xff08;略&#xff09; 3、安装mysql&#xff08;略&#xff09; 4、安装 解压 …

(十一)Servlet教程——Request请求转发

1.Web应用在处理客户端的请求的时候&#xff0c;一般的时候都需要多个资源协同处理&#xff0c;比如先经过一个Servlet的处理&#xff0c;然后再经过另外一个Servlet的处理。但是在一个Servlet中又不能直接调用另外一个Servlet的service方法&#xff0c;所以Servlet就提供了请求…

【嵌入式软件】LWIP 以太网通信

1.互联网模型 1.1 OSI&#xff08;Open System Interconnection&#xff09;七层模型 1&#xff09;应用层&#xff1a; 为上层用户提供应用的接口。常用的应用层的网络协议有&#xff1a;HTTP、FTP、TFTP、SMTP、SNMP、DNS、TELNET、HTTPS、POP3、DHCP 2&#xff09;表示层&…

【C++】C\C++内存管理

下面是围绕C\C内存管理这一块知识谈论相关的内存管理机制&#xff0c;有需要借鉴即可。 同时&#xff0c;我在下面也放了快速建立链表的模板&#xff0c;方便oj题目拿到vs上进行调试。 内存管理目录 1.CPP内存管理1.1new、delete关键字概念1.2特性1.3总结 2.new、delete的底层…

【C语言】贪吃蛇详解(附源码)

一、贪吃蛇实现效果 【C语言】贪吃蛇&#xff08;控制台&#xff09; 二、源码 &#x1f388;&#x1f388;&#x1f388;Snake 残风也想永存/C语言项目 - 码云 - 开源中国 (gitee.com)&#x1f388;&#x1f388;&#x1f388; 三、如何使用C语言去实现一个贪吃蛇&#xff1f…

每日一题:地下城游戏

恶魔们抓住了公主并将她关在了地下城 dungeon 的 右下角 。地下城是由 m x n 个房间组成的二维网格。我们英勇的骑士最初被安置在 左上角 的房间里&#xff0c;他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0…

ThreeJs 环境配置及遇到问题的解决方法

一、环境搭建 ThreeJs在实际在实际使用中更多的是结合框架开发例如&#xff1a;vue框架、react框架&#xff0c;在使用时需要配置开发环境&#xff0c;本文使用的是vscode ThreeJs NodeJs vue 1、ThreeJs安装 下载路径&#xff1a;GitHub - mrdoob/three.js: JavaScript…

CentOS命令大全:掌握关键命令及其精妙用法!

CentOS是一种流行的开源企业级Linux发行版&#xff0c;它基于Red Hat Enterprise Linux (RHEL)的源代码构建。对于系统管理员和运维工程师来说&#xff0c;掌握CentOS的常用命令至关重要。 这些命令不仅可以帮助管理服务器&#xff0c;还可以进行故障排查、性能监控和安全加固等…

【idea】idea 中 git 分支多个提交合并一个提交到新的分支

一、方法原理讲解 我们在 dev 分支对不同的代码文件做了多次提交。现在我们想要把这些提交都合并到 test 分支。首先我们要明白四个 git 操作&#xff0c; commit&#xff1a;命令用于将你的代码变更保存到本地代码仓库中&#xff0c;它创建了一个新的提交&#xff08;commit…

Ubuntu编译安装MariaDB并进行初始化配置

Ubuntu编译安装MariaDB并进行初始化配置 1. 编译安装MariaDB2. 配置MariaDB3. Docker安装MariaDB 1. 编译安装MariaDB MariaDB官方安装文档&#xff1a;https://mariadb.com/kb/en/Build_Environment_Setup_for_Linux/    下载MariaDB源码&#xff1a;https://mariadb.org/ma…

Spring Boot 如何实现缓存预热

Spring Boot 实现缓存预热 1、使用启动监听事件实现缓存预热。2、使用 PostConstruct 注解实现缓存预热。3、使用 CommandLineRunner 或 ApplicationRunner 实现缓存预热。4、通过实现 InitializingBean 接口&#xff0c;并重写 afterPropertiesSet 方法实现缓存预热。 1、使用…

TCP-模拟BS架构通信

简介 bs是通过浏览器进行访问的每次访问都会开启一个短期的socket用来访问服务器的资源 响应报文的格式 服务端 bs架构中的b是浏览器&#xff0c;不需要我们书写&#xff0c;我们只需要书写服务端即可 服务端 public class Server {public static void main(String[] args) {S…

[C++]22:C++11_part_one

C11 一.列表初始化&#xff1a;1.{}初始化&#xff1a;2.C11扩大了列表初始化的范围&#xff1a;3.std::initializer_list1.简单类型的列表初始化&#xff1a;2.复杂类型的列表初始化3.实现vector的列表初始化4.实现list的列表初始化&#xff1a;5.不支持列表初始化&#xff1a…

制作一个RISC-V的操作系统十六-系统调用

文章目录 用户态和内核态mstatus设置模式切换核心流程封装代码背景解释代码示例解析解释目的 用户态和内核态 mstatus设置 此时UIE设置为1和MPIE为1&#xff0c;MPP设置为0 代表当前权限允许UIE中断发生&#xff0c;并且在第一个mret后将权限恢复为用户态&#xff0c;同时MIE也…