0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

二叉树上应该怎么求

算法与数据结构 来源:代码随想录 作者:程序员Carl 2021-11-22 11:32 次阅读
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

二叉树上应该怎么求,二叉搜索树上又应该怎么求?

在求众数集合的时候有一个技巧,因为题目中众数是可以有多个的,所以一般的方法需要遍历两遍才能求出众数的集合。

但可以遍历一遍就可以求众数集合,使用了适时清空结果集的方法,这个方法还是很巧妙的。相信仔细读了文章的同学会惊呼其巧妙!

501.二叉搜索树中的众数

题目链接:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/solution

给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

  • 结点左子树中所含结点的值小于等于当前结点的值
  • 结点右子树中所含结点的值大于等于当前结点的值
  • 左子树和右子树都是二叉搜索树

例如:

给定 BST [1,null,2,2],

返回[2].

提示:如果众数超过1个,不需考虑输出顺序

进阶:你可以不使用额外的空间吗?(假设由递归产生的隐式调用栈的开销不被计算在内)

思路

这道题目呢,递归法我从两个维度来讲。

首先如果不是二叉搜索树的话,应该怎么解题,是二叉搜索树,又应该如何解题,两种方式做一个比较,可以加深大家对二叉树的理解。

递归法

如果不是二叉搜索树

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

具体步骤如下:

  1. 这个树都遍历了,用map统计频率

至于用前中后序那种遍历也不重要,因为就是要全遍历一遍,怎么个遍历法都行,层序遍历都没毛病!

这里采用前序遍历,代码如下:

//mapkey:元素,value:出现频率
voidsearchBST(TreeNode*cur,unordered_map<int,int>&map){//前序遍历
if(cur==NULL)return;
map[cur->val]++;//统计元素频率
searchBST(cur->left,map);
searchBST(cur->right,map);
return;
}
  1. 把统计的出来的出现频率(即map中的value)排个序

有的同学可能可以想直接对map中的value排序,还真做不到,C++中如果使用std::map或者std::multimap可以对key排序,但不能对value排序。

所以要把map转化数组即vector,再进行排序,当然vector里面放的也是pair类型的数据,第一个int为元素,第二个int为出现频率。

代码如下:

boolstaticcmp(constpair&a,constpair&b){
returna.second>b.second;//按照频率从大到小排序
}

vector>vec(map.begin(),map.end());
sort(vec.begin(),vec.end(),cmp);//给频率排个序
  1. 取前面高频的元素

此时数组vector中已经是存放着按照频率排好序的pair,那么把前面高频的元素取出来就可以了。

代码如下:

result.push_back(vec[0].first);
for(inti=1;i< vec.size(); i++) {
    //取最高的放到result数组中
if(vec[i].second==vec[0].second)result.push_back(vec[i].first);
elsebreak;
}
returnresult;

整体C++代码如下:

classSolution{
private:

voidsearchBST(TreeNode*cur,unordered_map<int,int>&map){//前序遍历
if(cur==NULL)return;
map[cur->val]++;//统计元素频率
searchBST(cur->left,map);
searchBST(cur->right,map);
return;
}
boolstaticcmp(constpair<int,int>&a,constpair<int,int>&b){
returna.second>b.second;
}
public:
vector<int>findMode(TreeNode*root){
unordered_map<int,int>map;//key:元素,value:出现频率
vector<int>result;
if(root==NULL)returnresult;
searchBST(root,map);
vectorint,int>>vec(map.begin(),map.end());
sort(vec.begin(),vec.end(),cmp);//给频率排个序
result.push_back(vec[0].first);
for(inti=1;i< vec.size(); i++) {
            //取最高的放到result数组中
if(vec[i].second==vec[0].second)result.push_back(vec[i].first);
elsebreak;
}
returnresult;
}
};

所以如果本题没有说是二叉搜索树的话,那么就按照上面的思路写!

是二叉搜索树

既然是搜索树,它中序遍历就是有序的

中序遍历代码如下:

voidsearchBST(TreeNode*cur){
if(cur==NULL)return;
searchBST(cur->left);//左
(处理节点)//中
searchBST(cur->right);//右
return;
}

遍历有序数组的元素出现频率,从头遍历,那么一定是相邻两个元素作比较,然后就把出现频率最高的元素输出就可以了。

关键是在有序数组上的话,好搞,在树上怎么搞呢?

这就考察对树的操作了。

二叉树:搜索树的最小绝对差中我们就使用了pre指针和cur指针的技巧,这次又用上了。

弄一个指针指向前一个节点,这样每次cur(当前节点)才能和pre(前一个节点)作比较。

而且初始化的时候pre = NULL,这样当pre为NULL时候,我们就知道这是比较的第一个元素。

代码如下:

if(pre==NULL){//第一个节点
count=1;//频率为1
}elseif(pre->val==cur->val){//与前一个节点数值相同
count++;
}else{//与前一个节点数值不同
count=1;
}
pre=cur;//更新上一个节点

此时又有问题了,因为要求最大频率的元素集合(注意是集合,不是一个元素,可以有多个众数),如果是数组上大家一般怎么办?

应该是先遍历一遍数组,找出最大频率(maxCount),然后再重新遍历一遍数组把出现频率为maxCount的元素放进集合。(因为众数有多个)

这种方式遍历了两遍数组。

那么我们遍历两遍二叉搜索树,把众数集合算出来也是可以的。

但这里其实只需要遍历一次就可以找到所有的众数。

那么如何只遍历一遍呢?

如果 频率count 等于 maxCount(最大频率),当然要把这个元素加入到结果集中(以下代码为result数组),代码如下:

if(count==maxCount){//如果和最大值相同,放进result中
result.push_back(cur->val);
}

是不是感觉这里有问题,result怎么能轻易就把元素放进去了呢,万一,这个maxCount此时还不是真正最大频率呢。

所以下面要做如下操作:

频率count 大于 maxCount的时候,不仅要更新maxCount,而且要清空结果集(以下代码为result数组),因为结果集之前的元素都失效了。

if(count>maxCount){//如果计数大于最大值
maxCount=count;//更新最大频率
result.clear();//很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}

关键代码都讲完了,完整代码如下:(只需要遍历一遍二叉搜索树,就求出了众数的集合

classSolution{
private:
intmaxCount;//最大频率
intcount;//统计频率
TreeNode*pre;
vector<int>result;
voidsearchBST(TreeNode*cur){
if(cur==NULL)return;

searchBST(cur->left);//左
//中
if(pre==NULL){//第一个节点
count=1;
}elseif(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,之前result里的元素都失效了
result.push_back(cur->val);
}

searchBST(cur->right);//右
return;
}

public:
vector<int>findMode(TreeNode*root){
count=0;
maxCount=0;
TreeNode*pre=NULL;//记录前一个节点
result.clear();

searchBST(root);
returnresult;
}
};

迭代法

只要把中序遍历转成迭代,中间节点的处理逻辑完全一样。

二叉树前中后序转迭代,传送门:

下面我给出其中的一种中序遍历的迭代法,其中间处理逻辑一点都没有变(我从递归法直接粘过来的代码,连注释都没改,哈哈)

代码如下:

classSolution{
public:
vector<int>findMode(TreeNode*root){
stackst;
TreeNode*cur=root;
TreeNode*pre=NULL;
intmaxCount=0;//最大频率
intcount=0;//统计频率
vector<int>result;
while(cur!=NULL||!st.empty()){
if(cur!=NULL){//指针来访问节点,访问到最底层
st.push(cur);//将访问的节点放进栈
cur=cur->left;//左
}else{
cur=st.top();
st.pop();//中
if(pre==NULL){//第一个节点
count=1;
}elseif(pre->val==cur->val){//与前一个节点数值相同
count++;
}else{//与前一个节点数值不同
count=1;
}
if(count==maxCount){//如果和最大值相同,放进result中
result.push_back(cur->val);
}

if(count>maxCount){//如果计数大于最大值频率
maxCount=count;//更新最大频率
result.clear();//很关键的一步,不要忘记清空result,之前result里的元素都失效了
result.push_back(cur->val);
}
pre=cur;
cur=cur->right;//右
}
}
returnresult;
}
};

总结

本题在递归法中,我给出了如果是普通二叉树,应该怎么求众数。

知道了普通二叉树的做法时候,我再进一步给出二叉搜索树又应该怎么求众数,这样鲜明的对比,相信会对二叉树又有更深层次的理解了。

在递归遍历二叉搜索树的过程中,我还介绍了一个统计最高出现频率元素集合的技巧, 要不然就要遍历两次二叉搜索树才能把这个最高出现频率元素的集合求出来。

为什么没有这个技巧一定要遍历两次呢?因为要求的是集合,会有多个众数,如果规定只有一个众数,那么就遍历一次稳稳的了。

最后我依然给出对应的迭代法,其实就是迭代法中序遍历的模板加上递归法中中间节点的处理逻辑,分分钟就可以写出来,中间逻辑的代码我都是从递归法中直接粘过来的。

求二叉搜索树中的众数其实是一道简单题,但大家可以发现我写了这么一大篇幅的文章来讲解,主要是为了尽量从各个角度对本题进剖析,帮助大家更快更深入理解二叉树

需要强调的是 leetcode上的耗时统计是非常不准确的,看个大概就行,一样的代码耗时可以差百分之50以上,所以leetcode的耗时统计别太当回事,知道理论上的效率优劣就行了。

其他语言版本

Java

暴力法

classSolution{
publicint[]findMode(FindModeInBinarySearchTree.TreeNoderoot){
Mapmap=newHashMap<>();
Listlist=newArrayList<>();
if(root==null)returnlist.stream().mapToInt(Integer::intValue).toArray();
//获得频率Map
searchBST(root,map);
List>mapList=map.entrySet().stream()
.sorted((c1,c2)->c2.getValue().compareTo(c1.getValue()))
.collect(Collectors.toList());
list.add(mapList.get(0).getKey());
//把频率最高的加入list
for(inti=1;i< mapList.size(); i++) {
   if(mapList.get(i).getValue()==mapList.get(i-1).getValue()){
list.add(mapList.get(i).getKey());
}else{
break;
}
}
returnlist.stream().mapToInt(Integer::intValue).toArray();
}

voidsearchBST(FindModeInBinarySearchTree.TreeNodecurr,Mapmap){
if(curr==null)return;
map.put(curr.val,map.getOrDefault(curr.val,0)+1);
searchBST(curr.left,map);
searchBST(curr.right,map);
}

}
classSolution{
ArrayListresList;
intmaxCount;
intcount;
TreeNodepre;

publicint[]findMode(TreeNoderoot){
resList=newArrayList<>();
maxCount=0;
count=0;
pre=null;
findMode1(root);
int[]res=newint[resList.size()];
for(inti=0;i< resList.size(); i++) {
            res[i] = resList.get(i);
        }
        returnres;
}

publicvoidfindMode1(TreeNoderoot){
if(root==null){
return;
}
findMode1(root.left);

introotValue=root.val;
//计数
if(pre==null||rootValue!=pre.val){
count=1;
}else{
count++;
}
//更新结果以及maxCount
if(count>maxCount){
resList.clear();
resList.add(rootValue);
maxCount=count;
}elseif(count==maxCount){
resList.add(rootValue);
}
pre=root;

findMode1(root.right);
}
}

Python

递归法

classSolution:
deffindMode(self,root:TreeNode)->List[int]:
ifnotroot:return
self.pre=root
self.count=0//统计频率
self.countMax=0//最大频率
self.res=[]
deffindNumber(root):
ifnotroot:returnNone//第一个节点
findNumber(root.left)//左
ifself.pre.val==root.val://中:与前一个节点数值相同
self.count+=1
else://与前一个节点数值不同
self.pre=root
self.count=1
ifself.count>self.countMax://如果计数大于最大值频率
self.countMax=self.count//更新最大频率
self.res=[root.val]//更新res
elifself.count==self.countMax://如果和最大值相同,放进res中
self.res.append(root.val)
findNumber(root.right)//右
return
findNumber(root)
returnself.res

迭代法-中序遍历-不使用额外空间,利用二叉搜索树特性

classSolution:
deffindMode(self,root:TreeNode)->List[int]:
stack=[]
cur=root
pre=None
maxCount,count=0,0
res=[]
whilecurorstack:
ifcur:#指针来访问节点,访问到最底层
stack.append(cur)
cur=cur.left
else:#逐一处理节点
cur=stack.pop()
ifpre==None:#第一个节点
count=1
elifpre.val==cur.val:#与前一个节点数值相同
count+=1
else:
count=1
ifcount==maxCount:
res.append(cur.val)
ifcount>maxCount:
maxCount=count
res.clear()
res.append(cur.val)

pre=cur
cur=cur.right
returnres


责任编辑:haq
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • SQL
    SQL
    +关注

    关注

    1

    文章

    789

    浏览量

    46368
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12862

原文标题:二叉搜索树中的众数是多少?

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏
加入交流群
微信小助手二维码

扫码添加小助手

加入工程师交流群

    评论

    相关推荐
    热点推荐

    C语言的常见算法

    = next; } return prev; } ``` ### 二叉树遍历 (前序) ```c struct TreeNode { int val; struct TreeNode
    发表于 11-24 08:29

    高频整流应该选用哪些MDD极管?核心参数与物理机制详解

    在高频整流应用中,选择什么MDD极管不是简单的“耐压够就行”,而是必须综合考虑反向恢复、正向压降、漏电、温升、浪涌能力,以及最终电路效率与EMI。许多工程师在几十kHz以下仍然可以采用普通整流管
    的头像 发表于 11-18 10:50 176次阅读
    高频整流<b class='flag-5'>应该</b>选用哪些MDD<b class='flag-5'>二</b>极管?核心参数与物理机制详解

    通过优化代码来提高MCU运行效率

    选择时间复杂度低的算法。 根据访问模式选择数据结构。频繁查找用哈希表,有序数据用二叉树等。 查表法:对于复杂的数学计算(如sin, log),或者协议解析,预先计算好结果存于数组中,用空间换时间
    发表于 11-12 08:21

    蜂鸟E203内核中断管理模块sirv_plic_man代码分析

    。 上面的代码生成一个二叉树结构来比较和选择具有最大优先级的挂起中断源及其ID。树状结构由级联比较器组成,每一层的比较器数量是前一层的一半。在树的每一层,选择优先级最高的中断并传递到下一层,直到只剩下
    发表于 10-23 06:05

    请问rtt studio 的文件夹打红什么意思?

    rtt studio 的文件夹打红什么意思?而且文件夹里面实际是有文件的,但是浏览不出来。
    发表于 09-18 06:34

    使用TC387芯片进行设计,请问引脚配置工具应该下载哪个板机支持包(bsp)呢?

    使用TC387芯片进行设计,请问引脚配置工具应该下载哪个板机支持包(bsp)呢?这个问题最近一直没有解决,大佬指点!
    发表于 08-11 07:53

    亿纬锂能荣获杭集团2022-2024年度优秀供应商奖

    近日,亿纬锂能凭借卓越产品、可靠交付与优质服务荣获杭集团颁发的“2022-2024年度优秀供应商”奖。杭集团副总经理兼杭电器董事长金华曙、杭电器总经理兼杭博电机总经理李明辉出席
    的头像 发表于 07-15 09:00 767次阅读

    下一代高速芯片晶体管解制造问题解决了!

    步骤造成的损坏,并且可以使用成熟的氧化硅材料和工具来构建。 由于现在的壁厚为 15 纳米,这可能会影响晶体管密度,因为外壁片器件比内壁片晶体管更大。然而,外壁片晶体管提供的可制
    发表于 06-20 10:40

    苏州高美达选购我司HS-TGA-101热重分析仪

    在材料研究与生产领域,精准分析材料热性能至关重要。苏州高美达公司经过多方调研与严格测试,最终选定我司的HS-TGA-101热重分析仪,为其材料研发与质量把控注入强大助力。苏州高美达热重分析(TG
    的头像 发表于 06-12 09:47 720次阅读
    苏州高<b class='flag-5'>求</b>美达选购我司HS-TGA-101热重分析仪

    taVNS经耳迷走神经电刺激适应症之缓解偏头痛

    神经血管系统与中枢敏化偏头痛的核心病理机制涉及三神经血管系统的异常激活。三神经节(TrigeminalGanglion)的C纤维释放降钙素基因相关肽(CGRP)和P物质,引发神经源性炎症和硬
    的头像 发表于 05-21 20:58 2114次阅读
    taVNS经耳迷走神经电刺激适应症之缓解偏头痛

    ADS1298前端的过压保护电路,极管只需要使用0.7V导通电压的普通极管吗?

    了,怎么还能起到保护作用? 3.因为电极与人体接触,是否需要使用ESD极管? 比如AVDD=2.5V,AVSS=-2.5V。我觉得应该使用VBR电压在2.5V以下的ESD极管,并且直接对地接就好了啊。
    发表于 01-06 06:27

    ATA-2041高压放大器在指形电极管状压电元件电极制备中的应用

    实验名称:指形电极管状压电元件电极制备与极化研究 测试目的:目前,已有学者对指形电极的管状压电元件进行了一系列研究,通过静力学方程完成理论推导,建立有限元分析模型,对该压电元件的静态驱动性能
    的头像 发表于 12-25 11:26 764次阅读
    ATA-2041高压放大器在<b class='flag-5'>叉</b>指形电极管状压电元件电极制备中的应用

    稳压极管故障排除方法 稳压极管的主要参数

    : 稳压极管故障排除方法 外观检查 : 检查稳压极管的封装是否有损坏,如裂纹、烧痕等。 检查引脚是否弯曲或断裂。 万用表检测 : 使用万用表的极管测试功能,测量稳压极管的正向和
    的头像 发表于 12-13 16:27 2810次阅读

    优良的充电桩电能表,应该拥有哪些指标?

    我们觉得应该有从环境耐受度、电表认证、电表校准方面考量。
    的头像 发表于 12-12 10:37 670次阅读
    优良的充电桩电能表,<b class='flag-5'>应该</b>拥有哪些指标?

    立功科技与远电子推出全新蓝牙汽车数字钥匙方案

    蓝牙6.0更新带来了高精度的CS(Channel Sounding)测距功能,为当前的蓝牙定位市场注入一股强劲动力。立功科技·远电子推出CS+多节点监听的全新蓝牙汽车数字钥匙方案,让丝滑的无感解锁刷新您的出行体验。
    的头像 发表于 12-10 16:39 2464次阅读
    立功科技与<b class='flag-5'>求</b>远电子推出全新蓝牙汽车数字钥匙方案