[LeetCode] 1157. Online Majority Element In Subarray 子数组中占绝大多数的元素
Design a data structure that efficiently finds the majority element of a given subarray.
The majority element of a subarray is an element that occurs threshold
times or more in the subarray.
Implementing the MajorityChecker
class:
MajorityChecker(int[] arr)
Initializes the instance of the class with the given arrayarr
.int query(int left, int right, int threshold)
returns the element in the subarrayarr[left...right]
that occurs at leastthreshold
times, or-1
if no such element exists.
Example 1:
Input
["MajorityChecker", "query", "query", "query"]
[[[1, 1, 2, 2, 1, 1]], [0, 5, 4], [0, 3, 3], [2, 3, 2]]
Output
[null, 1, -1, 2]
Explanation
MajorityChecker majorityChecker = new MajorityChecker([1, 1, 2, 2, 1, 1]);
majorityChecker.query(0, 5, 4); // return 1
majorityChecker.query(0, 3, 3); // return -1
majorityChecker.query(2, 3, 2); // return 2
Constraints:
1 <= arr.length <= 2 * 104
1 <= arr[i] <= 2 * 104
0 <= left <= right < arr.length
threshold <= right - left + 1
2 * threshold > right - left + 1
- At most
104
calls will be made toquery
.
这道题让设计一种数据结构可以有效的找出给定范围内子数组的多数,这里还给了一个阈值 threshold,只要出现次数大于等于这个阈值就算是多数。可能有些人看到这里就说,那还不简单么,遍历这个子数组区间,累加每个数字出现的次数呗,大于 threshold 就返回呗。如果就这么简单的话怎么对得起这道题 Hard 的身价,当然是不行的,得另寻更高效的解决方法。既然这里需要统计相同数字出现的次数,有一种以空间换时间的方法就是建立每个数字和其在原数组中出现的所有位置坐标组成的数组的映射,这样做的好处是可以快速知道任意一个数字出现的所有位置,而且坐标还是从小到大有序的,为之后的二分搜索法提供了条件。查找的时候遍历所有的数字,此时有了该数字在数组中出现的所有按顺序排列的坐标,可以用二分法查找第一个不小于左边界 left 的位置,然后再用二分法查找第一个大于右边界 right 的位置,若二者的差值大于等于 threshold,则说明该数字在区间 [left, right] 内至少出现了 threshold 次,返回该数字即可,否则返回 -1,参见代码如下:
解法一:
class MajorityChecker {
public:
MajorityChecker(vector<int>& arr) {
for (int i = 0; i < arr.size(); ++i) {
idxMap[arr[i]].push_back(i);
}
}
int query(int left, int right, int threshold) {
for (auto &a : idxMap) {
if (a.second.size() < threshold) continue;
auto it1 = lower_bound(begin(a.second), end(a.second), left);
auto it2 = upper_bound(begin(a.second), end(a.second), right);
if (it2 - it1 >= threshold) return a.first;
}
return -1;
}
private:
unordered_map<int, vector<int>> idxMap;
};
我们可以进行一些优化,比如先验证出现次数最多的数字,因为其更有可能会符合要求,这样的话可以给映射对儿进行排序,按照数字出现的次数从大到小排列,查找的方法还是不变的,参见代码如下:
解法二:
class MajorityChecker {
public:
MajorityChecker(vector<int>& arr) {
unordered_map<int, vector<int>> idxMap;
for (int i = 0; i < arr.size(); ++i) {
idxMap[arr[i]].push_back(i);
}
for (auto &a : idxMap) idxVec.push_back({a.first, a.second});
sort(begin(idxVec), end(idxVec), [](auto &a, auto &b) {return a.second.size() > b.second.size();});
}
int query(int left, int right, int threshold) {
for (auto &a : idxVec) {
if (a.second.size() < threshold) continue;
auto it1 = lower_bound(begin(a.second), end(a.second), left);
auto it2 = upper_bound(begin(a.second), end(a.second), right);
if (it2 - it1 >= threshold) return a.first;
}
return -1;
}
private:
vector<pair<int, vector<int>>> idxVec;
};
再来看一种优化方法,这里不是按照次数由多到少来选,而是用一种随机的取数方法,由于多数出现的个数是超过一半的,所以随机抽取一个数字是多数的概率是超过 50% 的,那么连续抽多次,不中的概率就变得非常非常小,可以忽略不计,这里将次数设置为 10 就可以了,除了随机选数这部分,剩余的跟之前的就没什么区别了,参见代码如下:
解法三:
class MajorityChecker {
public:
MajorityChecker(vector<int>& arr) {
for (int i = 0; i < arr.size(); ++i) {
idxMap[arr[i]].push_back(i);
}
nums = arr;
}
int query(int left, int right, int threshold) {
for (int i = 0; i < 10; ++i) {
auto a = idxMap.find(nums[left + rand() % (right - left + 1)]);
if (a->second.size() < threshold) continue;
auto it1 = lower_bound(begin(a->second), end(a->second), left);
auto it2 = upper_bound(begin(a->second), end(a->second), right);
if (it2 - it1 >= threshold) return a->first;
}
return -1;
}
private:
vector<int> nums;
unordered_map<int, vector<int>> idxMap;
};
Github 同步地址:
https://github.com/grandyang/leetcode/issues/1157
类似题目:
参考资料:
https://leetcode.com/problems/online-majority-element-in-subarray/
原文地址:https://www.cnblogs.com/grandyang/p/15010366.html
- PHP调用Go服务的正确方式 - Unix Domain Sockets
- 一条看似平常的报警邮件所做的分析(r8笔记第9天)
- 55. 上传文件(Web版) | 厚土Go学习笔记
- R语言与机器学习学习笔记(分类算法
- 54. 心跳的实现 | 厚土Go学习笔记
- 通过错误的sql来测试推理sql的解析过程(二) (r8笔记第7天)
- 53. Socket服务三次握手的示例 | 厚土Go学习笔记
- R分词继续,"不|知道|你在|说|什么"分词添加新词
- Java开发Spring第一天
- 关于R安装中文分词包安装不上的问题install.packages("tm")
- dataguard备库的数据文件的迁移实战(r8笔记第24天)
- Hive的left join、left outer join和left semi join三者的区别
- 52. Socket Server 自定义协议的简单实现 | 厚土Go学习笔记
- dataguard备库的数据文件的迁移(r8笔记第22天)
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- CentOS7.5安装配置Harbor1.7的全过程
- Linux CentOS 定时运行脚本配置的方法
- 从Centos7升级到Centos8的教程(图文详解)
- Linux(Centos7)下redis5集群搭建和使用说明详解
- CentOS7下安装yum源及上传下载命令rz、sz安装方法(图解)
- C#实例:四路激光测距雷达数据采集和波形图绘制
- Linux 中有效用户组和初始用户组的实现
- ubuntu 16.04 64位兼容32位程序三步曲
- crontab执行结果未通过发送mail通知用户的方法
- 如何将CentOS7升级至CentOS8(详细步骤)
- leetcode树之二叉树的所有路径
- 如何利用Bash脚本监控Linux的内存使用情况
- Ubuntu18 给terminal改个漂亮的命令行提示符的方法
- leetcode树之将有序数组转换为二叉搜索树
- 在 Ubuntu 上安装 Protobuf 3 的教程详解