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

    文章

    753

    浏览量

    44027
  • 二叉树
    +关注

    关注

    0

    文章

    74

    浏览量

    12302

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

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

收藏 人收藏

    评论

    相关推荐

    镭神智能机器人三向式3D SLAM无人叉车:重塑高位堆垛与窄通道仓储新境界

    在智能制造与智慧物流的浪潮中,镭神智能凭借其卓越的技术实力,推出了革命性的三向式3DSLAM无人叉车LXK12-B。这款无人叉车不仅集成了自主搬运、智能堆垛等功能,更以举升高度5m、货180
    的头像 发表于 10-26 08:03 67次阅读
    镭神智能机器人三向<b class='flag-5'>叉</b>式3D SLAM无人叉车:重塑高位堆垛与窄通道仓储新境界

    什么是默克尔树(Merkle Tree)?如何计算默克尔根?

    01 默克尔树的概念 默克尔树(Merkle Tree)是一种特殊的二叉树,它的每个节点都存储了一个数据块的哈希值。哈希值是一种可以将任意长度的数据转换为固定长度的字符串的算法,它具有唯一性和不可
    的头像 发表于 09-30 18:22 462次阅读
    什么是默克尔树(Merkle Tree)?如何计算默克尔根?

    【电磁兼容标准解析分享】汽车电子零部件EMC标准解析---你应该了解和知道的细节(

    【电磁兼容标准解析分享】汽车电子零部件EMC标准解析---你应该了解和知道的细节(
    的头像 发表于 08-08 08:17 3707次阅读
    【电磁兼容标准解析分享】汽车电子零部件EMC标准解析---你<b class='flag-5'>应该</b>了解和知道的细节(<b class='flag-5'>二</b>)

    指电极上覆盖敏感材料的阻值计算

    覆盖的敏感材料厚度超出指厚度时计算电阻,是否可以视作指电极指间电阻多个周期串联后与超出指厚度部分敏感材料电阻并联
    发表于 07-05 14:48

    指MOSFET器件静电防护鲁棒性提升技巧

    栅极接地NMOS是一种广泛应用的片上ESD器件结构,为达到特定ESD防护等级,一般会采用多指版图形式来减小器件占用的芯片面积。但是,多指栅极接地NMOS在ESD应力作用下,各个指难于做到均匀
    的头像 发表于 06-22 00:50 447次阅读
    多<b class='flag-5'>叉</b>指MOSFET器件静电防护鲁棒性提升技巧

    esp32c3运行examples/wifi/getting_started/softAP例子,设置密码后WIFI标志上显示一个,为什么?

    运行examples/wifi/getting_started/softAP例子,发现如果不设置密码可以正常连上,但设置密码后WIFI标志上显示一个,输入密码后无法连接
    发表于 06-06 06:42

    迅镭激光中标叉车行业龙头杭集团!

    中国制造业企业500强、中国民营企业500强——杭集团响应号召,更“新”设备,引入迅镭高功率激光切割设备,建设智能工厂,向世界展示中国制造的智慧与力量!
    的头像 发表于 04-19 14:47 296次阅读
    迅镭激光中标叉车行业龙头杭<b class='flag-5'>叉</b>集团!

    远电子推出一款基于MC33774的ESS工商储BMS方案

    立功科技·远电子推出的ESS工商业储能BMS方案,基于NXP的MC33774锂电池管理芯片设计,支持中小型储能的级架构和中大型储能的三级BMS架构。
    的头像 发表于 04-02 14:08 2046次阅读
    <b class='flag-5'>求</b>远电子推出一款基于MC33774的ESS工商储BMS方案

    哈夫曼编码怎么算 哈夫曼编码左边是0还是1

    二叉树,将出现频率高的字符用较短的编码表示,而出现频率低的字符则用较长的编码表示。通过这种方式,可以实现对数据进行高效的编码和解码。 下面我们将详细介绍哈夫曼编码的算法过程。 统计字符频率 在进行哈夫曼编码前,首先需
    的头像 发表于 01-30 11:27 2390次阅读

    台积电先进封装产能供不应

    因为AI芯片需求的大爆发,台积电先进封装产能供不应,而且产能供不应的状况可能延续到2025年;这是台积电总裁魏哲家在法人说明会上透露的。 而且台积电一直持续的扩张先进封装产能,但是依然不能满足AI的强劲需求;这在一定程度会使得其他相关封装厂商因为接受转单而受益。
    的头像 发表于 01-22 18:48 911次阅读

    堆的实现思路

    什么是堆? 堆是一种 基于树结构的数据结构,它是一棵二叉树 ,具有以下两个特点: 堆是一个完全二叉树,即除了最后一层,其他层都是满的,最后一层从左到右填满。 堆中每个节点都满足堆的特性,即父节点的值
    的头像 发表于 11-24 16:02 376次阅读
    堆的实现思路

    树与二叉树的定义

    树型结构 是一类重要的 非线性数据结构 ,其中以树和二叉树最为常用,直观来看,树是以分支关系定义的层次结构。树型结构在客观世界中广泛存在,比如人类社会中的祖辈关系,社会机构组织等等都可以用树来形象
    的头像 发表于 11-24 15:57 1233次阅读
    树与<b class='flag-5'>二叉树</b>的定义

    OP-TEE安全存储安全文件的格式

    时,默认情况下, 加密后的数据会被保存在/data/tee目录中。 安全存储功能使用 二叉树的方式来 保存加密后的文件。 当第一次使用安全存储功能创建用于保存敏感数据的安全文件时,OP-TEE将会在/data/tee目录中生成两个文件:dirf.db文件和以数字命名的文件。 dirf.db文
    的头像 发表于 11-21 11:49 681次阅读
    OP-TEE安全存储安全文件的格式

    极管电流计算

    如下图,极管D1的正向导通压降为0.7V, 流过极管D1的电流 。
    的头像 发表于 11-20 17:28 2748次阅读
    <b class='flag-5'>二</b>极管电流计算

    什么情况下需要布隆过滤器

    , gmail等邮箱垃圾邮件过滤功能 这几个例子有一个共同的特点:如何判断一个元素是否存在一个集合中? 常规思路 数组 链表 树、平衡二叉树、Trie Map (红黑树) 哈希表 虽然上面描述的这几种数据结构配合常见的排序、分搜索可以快速高效的处理绝大部
    的头像 发表于 11-11 11:37 622次阅读
    什么情况下需要布隆过滤器