当前位置: 首页 > news >正文

【代码随想录算法训练营第37期 day21 | LeetCode530.二叉搜索树的最小绝对差、501.二叉搜索树中的众数、236. 二叉树的最近公共祖先】

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

由于二叉搜索树的特性,中序遍历时,输出的序列是一个单调递增的序列。

转成数组来看,在一个有序数组中,任意两个非连续元素的差值可以分解为连续元素差值的和。

例如,对于有序数组中的三个元素 a[i],a[i+1], a[i+2],

差值 ∣a[i]−a[i+2]∣ 可以分解为 ∣a[i]−a[i+1]∣+∣a[i+1]−a[i+2]∣。

也就是说最小的差值只会在两两做差中产生,其他的两个元素的差值能转换成连续的差值的和,这显然不是最小的,所有我们只需要计算 i-1 和 i 的两个节点的差值,并记录这些差值的最小值即可。

class Solution {
private:
vector<int> vec;
void traversal(TreeNode* root) {if (root == NULL) return;traversal(root->left);vec.push_back(root->val); // 将二叉搜索树转换为有序数组traversal(root->right);
}
public:int getMinimumDifference(TreeNode* root) {vec.clear();traversal(root);if (vec.size() < 2) return 0;int result = INT_MAX;for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值result = min(result, vec[i] - vec[i-1]);}return result;}
};

上述的解法是转换成数组,有额外的空间开销,我们可以直接在二叉树上,按照中序遍历的顺序进行两两节点做差值。这里的难理解的点在于怎么记录上一个节点,这个上一个节点指的是按中序遍历的输出顺序的上一个。

以下是实现代码,在递归中如何记录前一个节点的指针,其实实现起来是很简单, pre = cur 这个操作就能实现,变量pre被用来保持对前一个访问过的节点的引用。初始化时,preNULL,表示还没有前一个节点。

class Solution {
private:
int result = INT_MAX;
TreeNode* pre = NULL;
void traversal(TreeNode* cur) {if (cur == NULL) return;traversal(cur->left);   // 左if (pre != NULL){       // 中result = min(result, cur->val - pre->val);}pre = cur; // 记录前一个traversal(cur->right);  // 右
}
public:int getMinimumDifference(TreeNode* root) {traversal(root);return result;}
};

501.二叉搜索树中的众数

如果不是二叉搜索树,最直观的方法一定是把这个树都遍历了,用map统计频率,把频率排个序,最后取前面高频的元素的集合。

但二叉树搜索树的中序遍历是有序的,这里还是利用这个特性,如果转成数组,这题的思路可以转换成,在这样一个非严格单增的有序序列中,找出现次数最多的数。

在这个序列中,相同的值都是挨在一块的,如11222334555。我们可以用两个指针,从头开始,两个两个比较,相同时count++,用maxcount维护一个最大的count。当count计数到和maxcount相等的时候,说明这个数的个数已经是已知的最大了,先将该数认为是众数,输出到结果集中,如果后续有更大的,再清空结果集,重新记录众数。

通过记录上一个节点,直接在树上进行两两比较的技巧和上一题一样。

我最开始的想法是在 cur 和 pre 两个节点的值不一致时再进行 maxcount 和 result 的判断和更新,写成如下形式:

if(pre == nullptr){count = 1;
}
if(pre != nullptr){if(cur->val == pre->val)count++;else{if(maxcount == count){result.push_back(pre->val);}if(maxcount < count){maxcount = count;result.clear();result.push_back(pre->val);}count = 1;}
}

但这会导致在结束递归之后无法处理最后一个元素的情况。具体来说,如果最后几个元素是众数,并且在完全遍历树之后发现它们的计数与 maxcount 相等,这种情况下代码没有进入 maxcount 的判断,再做一次检查来添加这些元素到结果集 result 中。因为在最后一次递归调用返回后,没有再对 precount 进行最终的比较和添加操作。

所以把这个判断以及更新放到外边,每次都进行判断和更新,这样更新的节点就得改成 cur 而不是 pre 。整体如下所示。

class Solution {
public:TreeNode* pre = nullptr;int count = 1;int maxcount = -1;void traversal(TreeNode* cur, vector<int>& result){if(cur == nullptr) return;traversal(cur->left, result);if(pre != nullptr && cur->val == pre->val) {count++;} else {count = 1;  // 遇到新值,计数重置}if(count == maxcount) {result.push_back(cur->val);} else if(count > maxcount) {maxcount = count;result.clear();  // 发现更频繁的元素,清空结果列表并重新开始result.push_back(cur->val);}pre = cur;  // 更新前一个节点为当前节点traversal(cur->right, result);}vector<int> findMode(TreeNode* root) {vector<int> result;traversal(root, result);return result;}
};

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

记录思路,摘自代码随想录:代码随想录 (programmercarl.com)

遇到这个题目首先想的是要是能自底向上查找就好了,这样就可以找到公共祖先了。

那么二叉树如何可以自底向上查找呢?

回溯啊,二叉树回溯的过程就是从低到上。

后序遍历(左右中)就是天然的回溯过程,可以根据左右子树的返回值,来处理中节点的逻辑。

接下来就看如何判断一个节点是节点q和节点p的公共祖先呢。

首先最容易想到的一个情况:如果找到一个节点,发现左子树出现结点p,右子树出现节点q,或者 左子树出现结点q,右子树出现节点p,那么该节点就是节点p和q的最近公共祖先。

判断逻辑是 如果递归遍历遇到q,就将q返回,遇到p 就将p返回,那么如果 左右子树的返回值都不为空,说明此时的中节点,一定是q 和p 的最近祖先。

那么有录友可能疑惑,会不会左子树 遇到q 返回,右子树也遇到q返回,这样并没有找到 q 和p的最近祖先。

这么想的录友,要审题了,题目强调:二叉树节点数值是不重复的,而且一定存在 q 和 p

但是很多人容易忽略一个情况,就是节点本身p(q),它拥有一个子孙节点q(p)。

其实情况一 和 情况二 代码实现过程都是一样的,也可以说,实现情况一的逻辑,顺便包含了情况二。

因为遇到 q 或者 p 就返回,这样也包含了 q 或者 p 本身就是 公共祖先的情况。

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

相关文章:

  • Java集合【超详细】
  • 实战经验分享之移动云快速部署Stable Diffusion SDXL 1.0
  • K8S中Prometheus+Grafana监控
  • Wpf 使用 Prism 实战开发Day30
  • YOLOv5训练自定义数据集模型的参数与指令说明
  • Flutter 中的 SliverFillRemaining 小部件:全面指南
  • Golang | Leetcode Golang题解之第120题三角形最小路径和
  • kafka-消费者组-发布订阅测试
  • linux同步搭建多台服务器
  • Caused by: java.lang.IllegalStateException
  • docker安装Mysql5.7版本
  • Visual Studio怎么用?
  • MySql每天从0开始生成特定规则自增编号
  • Llama模型家族之RLAIF 基于 AI 反馈的强化学习(六) RLAIF 代码实战
  • 开源大模型源代码
  • [微信小程序] 使用ES6特性Class后出现编译异常
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • CSS选择器——伪元素选择器之处理父元素高度及外边距溢出
  • ES6, React, Redux, Webpack写的一个爬 GitHub 的网页
  • Js基础知识(四) - js运行原理与机制
  • Linux快速配置 VIM 实现语法高亮 补全 缩进等功能
  • mysql常用命令汇总
  • 闭包--闭包之tab栏切换(四)
  • 前端面试之闭包
  • 区块链共识机制优缺点对比都是什么
  • 译有关态射的一切
  • 数据库巡检项
  • ​如何使用ArcGIS Pro制作渐变河流效果
  • ​数据结构之初始二叉树(3)
  • #【QT 5 调试软件后,发布相关:软件生成exe文件 + 文件打包】
  • #微信小程序:微信小程序常见的配置传旨
  • #我与Java虚拟机的故事#连载03:面试过的百度,滴滴,快手都问了这些问题
  • $jQuery 重写Alert样式方法
  • (1)常见O(n^2)排序算法解析
  • (2015)JS ES6 必知的十个 特性
  • (c语言+数据结构链表)项目:贪吃蛇
  • (MTK)java文件添加简单接口并配置相应的SELinux avc 权限笔记2
  • (rabbitmq的高级特性)消息可靠性
  • (分享)一个图片添加水印的小demo的页面,可自定义样式
  • (四)JPA - JQPL 实现增删改查
  • (转)Sql Server 保留几位小数的两种做法
  • ./和../以及/和~之间的区别
  • .bat批处理(六):替换字符串中匹配的子串
  • .NET MAUI Sqlite数据库操作(二)异步初始化方法
  • .NetCore+vue3上传图片 Multipart body length limit 16384 exceeded.
  • .NET连接MongoDB数据库实例教程
  • .NET应用架构设计:原则、模式与实践 目录预览
  • .net中应用SQL缓存(实例使用)
  • .vue文件怎么使用_vue调试工具vue-devtools的安装
  • ::
  • []指针
  • [23] 4K4D: Real-Time 4D View Synthesis at 4K Resolution
  • [AIGC] 深入浅出 Python中的`enumerate`函数
  • [AIR] NativeExtension在IOS下的开发实例 --- IOS项目的创建 (一)
  • [C#学习笔记]Newtonsoft.Json