有趣的问题系列-主元素问题

什么是主元素问题?

给定一个有\(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(n\log 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\n",s[top - 1]);
    return 0;
}

后记

这个问题真的很有趣,也验证了我思维的成长过程。两年前我只能想到\(O(n\log n)\)的做法,如今我却能已经独立思考出\(O(n)\)的做法。所以我在进步,我也会一直进步的。感触真的是颇深。

posted @ 2020-03-02 00:16  dbxxx  阅读(251)  评论(1编辑  收藏  举报