题目描述
实现一个MajorityChecker
的类,它应该具有下述几个 API:
MajorityChecker(int[] arr)
会用给定的数组arr
来构造一个 MajorityChecker 的实例。int query(int left, int right, int threshold)
有这么几个参数:0 <= left <= right < arr.length
表示数组arr
的子数组的长度。2 * threshold > right - left + 1
,也就是说阈值threshold
始终比子序列长度的一半还要大。
每次查询query(...)
会返回在arr[left], arr[left+1], ..., arr[right]
中至少出现阈值次数threshold
的元素,如果不存在这样的元素,就返回-1
。
样例
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 <= 20000
1 <= arr[i] <= 20000
- 对于每次查询,
0 <= left <= right < len(arr)
- 对于每次查询,
2 * threshold > right - left + 1
- 查询次数最多为
10000
算法1
(摩尔投票) 实例化: $O(1)$, 查询$O(n)$ (n为查询长度)
由于threshold
严格大于查询区间长度的一半, 所以我们可以先用摩尔投票法求出majority element, 再遍历整个区间求出 majority element 个数并判断是否 >= threshold.
关于摩尔投票请参见:
AcWing 52. 数组中出现次数超过一半的数字
Boyer–Moore majority vote algorithm
Java 代码
class MajorityChecker {
int[] arr;
public MajorityChecker(int[] arr) {
this.arr = arr;
}
public int query(int left, int right, int threshold) {
int res = -1;
int count = 0;
for (int i = left; i <= right; i++) {
if (count == 0) {
res = arr[i];
count++;
}
else {
if (arr[i] == res) count ++;
else count--;
}
}
count = 0;
for (int i = left; i <= right; i++) {
if (arr[i] == res) count++;
}
return count >= threshold ? res : -1;
}
}
/**
* Your MajorityChecker object will be instantiated and called as such:
* MajorityChecker obj = new MajorityChecker(arr);
* int param_1 = obj.query(left,right,threshold);
*/
因为查询次数就已经是n次了,每次查询也是O(n),复杂度是O(n^2)了
这个做法现在已经过不去了
这个算是比较暴力的做法了。
更好的暴力算法可以考虑分块。
这个复杂度满足要求么
这道题其实没有卡时间复杂度,所以随便做。但如果在竞赛中,目前最好的复杂度就是 $sqrt(n)$。
我看了一下最快的做法是用的随机算法。统计每一个数字在数组中出现的位置。然后在查询区间内随机选取一个数字,那么选到众数的概率大于1/2,然后在该数字所有出现的位置中二分查找上下边界,判断出现次数为众数。这样重复执行
n
次,出错的概率小于\frac {1}{2^n}