1157. 子数组中占绝大多数的元素
难度:困难
题目:
设计一个数据结构,有效地找到给定子数组的 多数元素 。
子数组的 多数元素 是在子数组中出现 threshold
次数或次数以上的元素。
实现 MajorityChecker
类:
MajorityChecker(int[] arr)
会用给定的数组arr
对MajorityChecker
初始化。int query(int left, int right, int threshold)
返回子数组中的元素arr[left...right]
至少出现threshold
次数,如果不存在这样的元素则返回-1
。
示例 1:
输入:
["MajorityChecker", "query", "query", "query"]
[[[1, 1, 2, 2, 1, 1]], [0, 5, 4], [0, 3, 3], [2, 3, 2]]
输出:
[null, 1, -1, 2]
解释:
MajorityChecker majorityChecker = new MajorityChecker([1,1,2,2,1,1]);
majorityChecker.query(0,5,4); // 返回 1
majorityChecker.query(0,3,3); // 返回 -1
majorityChecker.query(2,3,2); // 返回 2
提示:
1 <= arr.length <= 2 * 104
1 <= arr[i] <= 2 * 104
0 <= left <= right < arr.length
threshold <= right - left + 1
2 * threshold > right - left + 1
- 调用
query
的次数最多为104
个人思路
思路与算法
解法一、暴力
问题分为两个部分:
找出 可能的 绝对众数(不一定存在绝对众数,但存在绝对众数的话找到的一定是绝对众数)和统计这个数出现的次数。
找可能的绝对众数方法很简单,暴力寻找即可(类似于打擂台,具体实现可以看代码,正确性证明略去)。
统计次数也使用暴力统计。
设数组长度为 n,询问次数为 q,则总时间复杂度为 O(nq),无法通过。
代码
class MajorityChecker {
int n,a[20005];
public:
MajorityChecker(vector<int>& arr) {
n=arr.size();
for(int i=0;i<n;i++)a[i]=arr[i];
}
int query(int left, int right, int threshold) {
int i,j,k;
j=k=0;
for(i=left;i<=right;i++)if(a[i]==j)k++;
else if(k)k--;
else
{
j=a[i];
k=1;
}
for(i=left,k=0;i<=right;i++)if(a[i]==j)k++;
if(k<threshold)j=-1;
return j;
}
};
解法二:线段树
注意到暴力算法维护的信息满足可加性(即可以快速合并两个子段的信息得到完整段的信息),因此可以使用线段树维护。具体实现可以参见代码。因此寻找可能的绝对众数的时间复杂度为 O(logn)。
数值范围是 [1,20000],因此使用 20000 个 vector 存储每一个数出现的位置,使用 lower_bound 和 upper_bound 即可在 O(logn) 的时间复杂度内找出一个数在一个区间内的出现次数。
因此,单次询问的时间复杂度为 O(logn),预处理时间复杂度为 O(n),总时间复杂度为 O(n+qlogn)。(貌似由于常数 + 数据原因,分块比线段树还快)
代码
class MajorityChecker {
struct node
{
int x,y;
node operator+(const node& b)const
{
node t;
if(x==b.x)
{
t.x=x;
t.y=y+b.y;
}
else if(y<b.y)
{
t.x=b.x;
t.y=b.y-y;
}
else
{
t.x=x;
t.y=y-b.y;
}
return t;
}
}t[65536];
int n,a[20005];
vector<int> s[20005];
void build(int R,int l,int r)
{
if(l==r)
{
t[R].x=a[l];
t[R].y=1;
return;
}
int mid=l+r>>1;
build(R<<1,l,mid);
build(R<<1|1,mid+1,r);
t[R]=t[R<<1]+t[R<<1|1];
}
node ask(int R,int l,int r,int l1,int r1)
{
if(l1==l&&r==r1)return t[R];
int mid=l+r>>1;
if(r1<=mid)return ask(R<<1,l,mid,l1,r1);
if(l1>mid)return ask(R<<1|1,mid+1,r,l1,r1);
return ask(R<<1,l,mid,l1,mid)+ask(R<<1|1,mid+1,r,mid+1,r1);
}
public:
MajorityChecker(vector<int>& arr) {
n=arr.size();
int i;
for(i=0;i<n;i++)s[a[i]=arr[i]].push_back(i);
build(1,0,n-1);
}
int query(int left, int right, int threshold) {
int ans=ask(1,0,n-1,left,right).x;
if(upper_bound(s[ans].begin(),s[ans].end(),right)-lower_bound(s[ans].begin(),s[ans].end(),left)<threshold)ans=-1;
return ans;
}
};
每天记录一下做题思路。