代码随想录算法训练营第16天 | 第六章 二叉树 part06

2024-08-26 07:44

本文主要是介绍代码随想录算法训练营第16天 | 第六章 二叉树 part06,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

代码随想录算法训练营第16天 | 第六章 二叉树 part06

    • 530.二叉搜索树的最小绝对差
    • 501.二叉搜索树中的众数
      • 普通二叉树
      • 搜索二叉树
    • 236. 二叉树的最近公共祖先

530.二叉搜索树的最小绝对差

需要领悟一下二叉树遍历上双指针操作,优先掌握递归
题目链接/文章讲解:link
视频讲解:link

/*** 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:int result =INT_MAX;TreeNode* pre;void traversal(TreeNode* cur){if (cur==nullptr)return;traversal(cur->left);if (pre!=nullptr)result=abs(cur->val-pre->val)>result?result:abs(cur->val-pre->val);pre=cur;traversal(cur->right);}int getMinimumDifference(TreeNode* root) {traversal(root);return result;}
};

中序遍历能够按照递增顺序访问节点的值。因此,最小绝对差一定是相邻节点之间的差值,写的时候才注意到,完全没必要用abs函数求绝对值。

int getMinimumDifference(TreeNode* root) {stack<TreeNode*> stk;TreeNode* cur = root;TreeNode* pre = nullptr;int result = INT_MAX;while (!stk.empty() || cur != nullptr) {while (cur != nullptr) {stk.push(cur);cur = cur->left;}cur = stk.top();stk.pop();  if (pre != nullptr) {result = min(result, abs(cur->val - pre->val));}pre = cur;        cur = cur->right;}return result;
}

使用栈来实现中序遍历的迭代版本,从而避免栈溢出问题。同时避免代码中使用了全局变量 pre 和 result,这可以将它们作为函数参数传递=。

501.二叉搜索树中的众数

和 530差不多双指针思路,不过 这里涉及到一个很巧妙的代码技巧。
如果不是二叉搜索树,我也想到的最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。
可以先自己做做看,然后看视频讲解。

普通二叉树

这也是一个难题,如果树中有不止一个众数,可以按 任意顺序 返回。遍历 unordered_map,寻找频率最大的节点值。使用 keyVector.clear() 清空之前的众数,如果发现有更新的更大频率值,就将新的值加入 vector。如果遇到相同频率的值,则追加到 keyVector

link
视频讲解:link

/*** 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:
void searchBST(TreeNode* cur, unordered_map<int, int>& map) { // 前序遍历if (cur == NULL) return ;map[cur->val]++; // 统计元素频率searchBST(cur->left, map);searchBST(cur->right, map);return ;
}                  vector<int> findMode(TreeNode* root) {std::unordered_map<int, int> map;searchBST(root, map);int maxKey = map.begin()->second;  // 初始将第一个键作为最大值std::vector<int> keyVector; // 遍历unordered_mapfor (const auto& pair : map) {if (pair.second > maxKey) {maxKey = pair.second;     keyVector.clear();   keyVector.push_back(pair.first);  } else if (pair.second == maxKey) {keyVector.push_back(pair.first);}}return keyVector;}
};

使用 unordered_map<int, int> 来存储每个节点值以及它出现的次数。map[cur->val]++ 这一步递增节点值 val 的频率。

搜索二叉树

想到搜索二叉树,首先想到中序遍历,每一次 弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组).频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。
count = 0;
maxCount = 0;
pre = NULL;
result.clear();//看了代码随想录的代码,确实不能忘了全局变量初始化,因为我们写的是子函数,得要被调用很多次。第一次被用完,再一次调用要记得初始化。

代码看着挺麻烦,实际上只要把思路给搞懂,仅仅是麻烦不复杂。中序遍历,判断最大清空或者导入数组,就这两部分。外加上全局变量的设定和初始化。

/*** 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* pre;
int maxCount = 0; 
int count = 0; 
vector<int> result;
void searchBST(TreeNode* cur) {if (cur == NULL) return ;searchBST(cur->left); if (pre == NULL) { count = 1; } else if (pre->val == cur->val) { count++;} else { count = 1;}pre = cur; // 更新上一个节点if (count == maxCount) { // 如果和最大值相同,放进result中result.push_back(cur->val);}if (count > maxCount) { // 如果计数大于最大值频率maxCount = count;  result.clear();     result.push_back(cur->val);}      searchBST(cur->right);      return;}vector<int> findMode(TreeNode* root) {count = 0;maxCount = 0;pre = nullptr; // 记录前一个节点result.clear();searchBST(root);return result;}
};

236. 二叉树的最近公共祖先

本题其实是比较难的,可以先看视频讲解

link
视频讲解:link
先想下思路如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。
在这里插入图片描述
最小公共祖先,需要从底向上遍历,那么二叉树,只能通过后序遍历(即:回溯)实现从底向上的遍历方式。

在回溯的过程中,必然要遍历整棵二叉树,即使已经找到结果了,依然要把其他节点遍历完,因为要使用递归函数的返回值(也就是代码中的left和right)做逻辑判断。

要理解如果返回值left为空,right不为空为什么要返回right,为什么可以用返回right传给上一层结果

class Solution {
public:TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {if (root == q || root == p || root == NULL) return root;TreeNode* left = lowestCommonAncestor(root->left, p, q);TreeNode* right = lowestCommonAncestor(root->right, p, q);if (left != NULL && right != NULL) return root;if (left == NULL && right != NULL) return right;else if (left != NULL && right == NULL) return left;else  { //  (left == NULL && right == NULL)return NULL;}}
};

这题很巧妙,如果当前节点是 p 或 q,或者当前节点为空(root == NULL),直接返回当前节点作为结果。当前节点如果是其中一个目标节点,说明找到了一个目标节点,此时可能是公共祖先。 递归地在左子树 root->left 和右子树 root->right 上分别查找目标节点 p 和 q,将结果分别存储在 left 和 right。就是一层层往下查。 如果发现左边找到了一个,右边找到了一个,那就返回当前结点。 如果发现都是在左边找到的如果 left != NULL && right != NULL,说明 p 和 q 分别在当前节点的左右子树中,所以当前节点 root 就是它们的最近公共祖先。 如果 left == NULL && right != NULL,说明 p 和 q 都在右子树中,返回 right 作为结果。 如果 left != NULL && right == NULL,说明 p 和 q 都在左子树中,返回 left 作为结果。 如果 left == NULL && right == NULL,说明在当前节点的子树中没有找到 p 或 q,返回 NULL。

这篇关于代码随想录算法训练营第16天 | 第六章 二叉树 part06的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1107962

相关文章

利用Python调试串口的示例代码

《利用Python调试串口的示例代码》在嵌入式开发、物联网设备调试过程中,串口通信是最基础的调试手段本文将带你用Python+ttkbootstrap打造一款高颜值、多功能的串口调试助手,需要的可以了... 目录概述:为什么需要专业的串口调试工具项目架构设计1.1 技术栈选型1.2 关键类说明1.3 线程模

Python Transformers库(NLP处理库)案例代码讲解

《PythonTransformers库(NLP处理库)案例代码讲解》本文介绍transformers库的全面讲解,包含基础知识、高级用法、案例代码及学习路径,内容经过组织,适合不同阶段的学习者,对... 目录一、基础知识1. Transformers 库简介2. 安装与环境配置3. 快速上手示例二、核心模

Java的栈与队列实现代码解析

《Java的栈与队列实现代码解析》栈是常见的线性数据结构,栈的特点是以先进后出的形式,后进先出,先进后出,分为栈底和栈顶,栈应用于内存的分配,表达式求值,存储临时的数据和方法的调用等,本文给大家介绍J... 目录栈的概念(Stack)栈的实现代码队列(Queue)模拟实现队列(双链表实现)循环队列(循环数组

使用Java将DOCX文档解析为Markdown文档的代码实现

《使用Java将DOCX文档解析为Markdown文档的代码实现》在现代文档处理中,Markdown(MD)因其简洁的语法和良好的可读性,逐渐成为开发者、技术写作者和内容创作者的首选格式,然而,许多文... 目录引言1. 工具和库介绍2. 安装依赖库3. 使用Apache POI解析DOCX文档4. 将解析

C++使用printf语句实现进制转换的示例代码

《C++使用printf语句实现进制转换的示例代码》在C语言中,printf函数可以直接实现部分进制转换功能,通过格式说明符(formatspecifier)快速输出不同进制的数值,下面给大家分享C+... 目录一、printf 原生支持的进制转换1. 十进制、八进制、十六进制转换2. 显示进制前缀3. 指

openCV中KNN算法的实现

《openCV中KNN算法的实现》KNN算法是一种简单且常用的分类算法,本文主要介绍了openCV中KNN算法的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的... 目录KNN算法流程使用OpenCV实现KNNOpenCV 是一个开源的跨平台计算机视觉库,它提供了各

使用Python实现全能手机虚拟键盘的示例代码

《使用Python实现全能手机虚拟键盘的示例代码》在数字化办公时代,你是否遇到过这样的场景:会议室投影电脑突然键盘失灵、躺在沙发上想远程控制书房电脑、或者需要给长辈远程协助操作?今天我要分享的Pyth... 目录一、项目概述:不止于键盘的远程控制方案1.1 创新价值1.2 技术栈全景二、需求实现步骤一、需求

Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码

《Java中Date、LocalDate、LocalDateTime、LocalTime、时间戳之间的相互转换代码》:本文主要介绍Java中日期时间转换的多种方法,包括将Date转换为LocalD... 目录一、Date转LocalDateTime二、Date转LocalDate三、LocalDateTim

jupyter代码块没有运行图标的解决方案

《jupyter代码块没有运行图标的解决方案》:本文主要介绍jupyter代码块没有运行图标的解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录jupyter代码块没有运行图标的解决1.找到Jupyter notebook的系统配置文件2.这时候一般会搜索到

Python通过模块化开发优化代码的技巧分享

《Python通过模块化开发优化代码的技巧分享》模块化开发就是把代码拆成一个个“零件”,该封装封装,该拆分拆分,下面小编就来和大家简单聊聊python如何用模块化开发进行代码优化吧... 目录什么是模块化开发如何拆分代码改进版:拆分成模块让模块更强大:使用 __init__.py你一定会遇到的问题模www.