什么是主元素问题?
给定一个有(n)个元素的数列,保证其中有一个数出现的次数严格大于(50\%),求这个数。
怎么做?
朴素桶计数
我们第一想到的,至少我第一想到的就是桶计数。出现一个数,把它扔进桶中。桶计数是很基本的概念,如果你这都不懂,赶紧百度去!
代码实现也很简单,纯手敲吧,有锅不修。
#include <iostream>
#include <cstdio>
int bucket[10086];
int main() {
int n;
std :: cin >> n;
for(int i = 1; i <= n; i++) {
int x;
std :: cin >> x;
bucket[x]++;
}
for(int i = 1; i <= 10086; i++)
if(bucket[i] > n / 2) {
cout << i << std :: endl;
break;
}
return 0;
}
但是桶计数的局限性非常大,非常容易爆空间,int的范围就可以让这玩意儿直接gg。
如果你想到了离散化,说明你还是很聪明的,蟹蟹为你点赞。但是离散化并不是最好的方法,照样有很多东西会卡离散化。我们直接pass掉桶计数吧。
好吧我知道有些人又开始叫map了……稍安勿躁,我有更好的方案。
排序
这叫做更好的方案????蟹蟹你真是绝了。
先别急着爆踩我,首先一个数列如果存在主元素,则排序后第(dfrac{n}{2})个位置上的数一定是主元素。
我们就有了新方法:
#include <iostream>
#include <cstdio>
const int maxn = 10005;
int a[maxn];
int main() {
int n;
for(int i = 1; i <= n; i++)
std :: cin >> a[i];
sort(a + 1, a + n + 1);
std :: cout << a[n / 2 + 1];
return 0;
}
很明显此算法的复杂度是(O(nlog n)),但还有没有直接(O(n))的做法呢?
还真有。以前还是个初学者的时候看到主元素问题思路就停止在这里了,前天有机会重新见到了这道题,我又有了新的想法。
互相抵消法
我们把序列中所有不相同的两个数互相抵消,剩下的就是主元素。因为主元素在数列中出现的数量严格大于(50\%),所以这个做法的正确性不难保证。这个方法非常的微妙。
这里可以用很多种实现方式,我选择用stack
食用= =
#include <iostream>
#include <cstdio>
const int maxn = 10000005;
int s[maxn];
int top;
int main() {
int n;
scanf("%d",&n);
while(n--) {
int x;
scanf("%d",&x);
s[top++] = x;
if(top == 1) continue;
if(s[top - 1] != s[top - 2]) top -= 2;
}
printf("%d
",s[top - 1]);
return 0;
}
后记
这个问题真的很有趣,也验证了我思维的成长过程。两年前我只能想到(O(nlog n))的做法,如今我却能已经独立思考出(O(n))的做法。所以我在进步,我也会一直进步的。感触真的是颇深。