C++力扣题目501--二叉搜索树中的众数

给你一个含重复值的二叉搜索树(BST)的根节点 root ,找出并返回 BST 中的所有 众数(即,出现频率最高的元素)。

如果树中有不止一个众数,可以按 任意顺序 返回。

假定 BST 满足如下定义:

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

示例 1:

输入:root = [1,null,2,2]
输出:[2]

示例 2:

输入:root = [0]
输出:[0]
提示:
  • 树中节点的数目在范围 [1, 104] 内
  • -105 <= Node.val <= 105

 

思路

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

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

#递归法

#如果不是二叉搜索树

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

具体步骤如下:

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

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

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

// map<int, int> key:元素,value:出现频率
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 ;
}

  1. 把统计的出来的出现频率(即map中的value)排个序

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

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

代码如下:

bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {return a.second > b.second; // 按照频率从大到小排序
}vector<pair<int, int>> vec(map.begin(), map.end());
sort(vec.begin(), vec.end(), cmp); // 给频率排个序
  1. 取前面高频的元素

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

代码如下:

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

整体C++代码如下:

class Solution {
private: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 ;
}
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {return a.second > b.second;
}
public:vector<int> findMode(TreeNode* root) {unordered_map<int, int> map; // key:元素,value:出现频率vector<int> result;if (root == NULL) return result;searchBST(root, map);vector<pair<int, int>> vec(map.begin(), map.end());sort(vec.begin(), vec.end(), cmp); // 给频率排个序result.push_back(vec[0].first);for (int i = 1; i < vec.size(); i++) {// 取最高的放到result数组中if (vec[i].second == vec[0].second) result.push_back(vec[i].first);else break;}return result;}
};

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

#是二叉搜索树

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

如图:

501.二叉搜索树中的众数1

中序遍历代码如下:

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

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

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

这就考察对树的操作了。

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

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

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

代码如下:

if (pre == NULL) { // 第一个节点count = 1; // 频率为1
} else if (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);
}

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

class Solution {
private:int maxCount = 0; // 最大频率int count = 0; // 统计频率TreeNode* pre = NULL;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,之前result里的元素都失效了result.push_back(cur->val);}searchBST(cur->right);      // 右return ;}public:vector<int> findMode(TreeNode* root) {count = 0;maxCount = 0;pre = NULL; // 记录前一个节点result.clear();searchBST(root);return result;}
};

#迭代法

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

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

  • 二叉树:前中后序迭代法(opens new window)
  • 二叉树:前中后序统一风格的迭代方式(opens new window)

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

代码如下:

class Solution {
public:vector<int> findMode(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;TreeNode* pre = NULL;int maxCount = 0; // 最大频率int count = 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;} else if (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;               // 右}}return result;}
};

#总结

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

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

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

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

中国X射线管行业研究与投资预测报告(2024版)

内容介绍&#xff1a; X射线管俗称球管&#xff0c;是X射线机的核心部件。X射线管在医学上可用于诊断和治疗&#xff0c;在工业技术方面可用于材料的无损检测、结构分析、光谱分析和底片曝光等。 目前&#xff0c;X射线管的壳体材料主要采用玻璃、陶瓷、金属材料这三种材质。玻…

项目中Ant Design Pro业务问题解决方案

ProTable实现多选反显筛选项多级关联选择 import {forwardRef,useImperativeHandle,useEffect,useRef,useReducer, } from "react"; import { Drawer, Space, Button, message } from "antd"; import * as PC from "ant-design/pro-components";…

Berstagram ——模拟

Polycarp最近注册了一个新的社交网络Berstagram。他立即在那里发表了 n 篇帖子。他给所有帖子分配了从 1 到 n 的数字&#xff0c;并逐一发布。所以&#xff0c;就在发布Polycarp的新闻推送后&#xff0c;包含了从 1 到 n 的帖子——最高的帖子编号为 1&#xff0c;下一个帖子编…

Snakemake:初探

我已经安装了mamba&#xff08;没有的话可以这样试试&#xff09; curl -L https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -o Mambaforge-Linux-x86_64.sh bash Mambaforge-Linux-x86_64.sh 安装snakemake Setup - Snakem…

PHP项目添加分布式锁,这里是ThinkPHP8框架实现分布式锁

背景&#xff1a;公司旧项目&#xff0c;最初访问量不多&#xff0c;单机部署的。后来&#xff0c;访问量上来了&#xff0c;有阵子很卡&#xff0c;公司决定横向扩展&#xff0c;后端代码部署了三台服务器。部署调整后&#xff0c;有用户反馈&#xff0c;一个订单支付了三次。…

【Java高级应用:深入探索Java编程的强大功能,JVM 类加载机制, JVM 内存模型,垃圾回收机制,JVM 字节码执行,异常处理机制】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

网络原理--http

目录 一、 DNS&#xff08;应用层协议&#xff09; 1、域名概念 2、维护ip地址和域名之间的映射&#xff08;域名解析系统&#xff09; 3、DNS系统&#xff08;服务器&#xff09; 4、如何解决DNS服务器高并发问题 二、HTTP&#xff08;应用层协议&#xff09; 1、htt…

开源云真机平台-Sonic平台-python自定义脚本-config.json方式实现全局配置参数的读写操作

【主要功能】 config.json方式实现全局配置参数的读写操作 使用python实现以下功能&#xff1a; 1、使用将接口获取的变量值&#xff0c;写入到当前目录下的config文件中&#xff0c;如delayTime10&#xff1b; 2、读取当前目录下的config文件中&#xff0c;特定变量的值&…

修改默认负载均衡策略(Ribbon)

修改方式 配置类配置文件 配置类 增加配置类&#xff1a;配置类必须拥有 Configuration 且不能在 ComponentScan 包下 package com.learning.springcloud.config; import com.alibaba.cloud.nacos.ribbon.NacosRule; import com.netflix.loadbalancer.IRule; import org.sp…

vue3深入组件:组件事件

触发与监听事件 在组件的模板表达式中&#xff0c;可以直接使用 $emit 方法触发自定义事件&#xff1a; <button click"$emit(someEvent)">click me</button>父组件可以通过 v-on (缩写为 ) 来监听事件&#xff1a; <MyComponent some-event"…

fs 模块

1. fs写文件 1.1 普通写入 const fs require(fs) // 异步写入 fs.writeFile(./hello.txt,刚刚我错过的大雨,err>{console.log(err); }) // 同步写入 fs.writeFileSync(./text.txt,hello boy!)1.2 文件的追加写入 const fs require(fs) fs.appendFile(./text.txt,\r\r\r…

【华为OD机试真题2023CD卷 JAVAJS】高效货运

华为OD2023(C&D卷)机试题库全覆盖,刷题指南点这里 高效货运 知识点递归循环 时间限制:1s 空间限制:32MB 限定语言:不限 题目描述: 1.老李是货运公司承运人,老李的货车额定载货重量为wt 2.现有两种货物,货物A单件重量为wa,单件运费利润为pa,货物B单件重量为wb,单…

0115qt聊天室客户端+数据库

#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QSqlDatabase>//数据库管理类 #include <QSqlQuery>//执行sql语句类 #include <QSqlRecord>//数据库记录类 #include <QSqlError>//数据库错误类 #include <QMessageBox>/…

win10安装ssh服务

前置条件&#xff1a; 远程虚拟机&#xff0c;防火墙关闭&#xff0c;本地主机与虚拟机互相可以ping通 虚拟机是win10专业版本 操作步骤&#xff1a; 1、搜索框搜索“Windows PowerShell”并以管理员身份运行 2、输入如下代码&#xff0c;检查本地是否有ssh服务&#xff0c…

【Docker】网络模式➕自定义网络

&#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 接下来看看由辉辉所写的关于Docker的相关操作吧 目录 &#x1f973;&#x1f973;Welcome 的Huihuis Code World ! !&#x1f973;&#x1f973; 一.Docker网络模式的介绍 二. 网桥模…

Linux内核架构和工作原理详解(二)

Linux内核体系结构简析简析 图1 Linux系统层次结构 最上面是用户&#xff08;或应用程序&#xff09;空间。这是用户应用程序执行的地方。用户空间之下是内核空间&#xff0c;Linux 内核正是位于这里。GNU C Library &#xff08;glibc&#xff09;也在这里。它提供了连接内核…

【react】记录一次react组件props依赖异步数据(setFieldsValue)后传递form给子组件,再逐层传递给孙子组件引起的未渲染异常

背景 react祖父组件设置异步数据&#xff08;setFieldsValue&#xff09;后传递form给子组件&#xff0c;再逐层传递给孙子组件引起的未渲染异常&#xff0c;孙子组件如果不设置useEffect和useState去监听value的值进行重渲染&#xff0c;会出现获取得到value最新值&#xff0…

【JVM】类的生命周期

目录 类的生命周期 加载阶段 连接阶段 初始化阶段 类的使用阶段 类的加载阶段 类的生命周期 加载阶段 在加载阶段&#xff0c;类加载器首先会通过一个类的全限定名来获取定义此类的二进制字节流。这个步骤主要是将整个Class 文件解析成二进制流。 &#xff08;全限定名是…

海外代理IP怎么用?常见使用问题及解决方案

海外代理IP是指提供全球范围内的代理服务器&#xff0c;代理服务器充当IP与目标网站之间的中介&#xff0c;可以起到安全匿名、提高网速、突破网络壁垒的作用。在使用代理IP的过程中&#xff0c;用户可能会遇到各种挑战&#xff0c;如连接问题、速度慢等。理解这些问题的原因并…