摩尔投票(绝对众数)

绝对众数:数组内出现次数大于 n2 的数。

求绝对众数的方法:
暴力做法 O(nlogn) 排序并枚举左端点。

摩尔投票:O(n) 求出。

摩尔投票

image

丢个模板。


            int now = -1; int cnt = 0;
            f(j, 1, n) {
                if(x[j] != now) cnt--;
                else cnt++;
                if(cnt < 0) {
                    now = x[j]; cntx = 1;
                }
            }
            int ccnt = 0;
            f(j, 1, n) {
                if(x[j] == now) ccnt++;
            }
            if (ccnt >= len){
                cout << cnt << endl; 
            }   

注意,序列存在区间众数时,一定是 cnt。序列不存在区间众数时,cnt 随机。所以还要再扫一遍。

拓展到 n / k

https://leetcode.cn/problems/majority-element-ii/

可以证明,出现次数超过 n/k 的数最多只有 k1 个。否则必然违背「数总共只有 n 个」或者「当前统计的是出现次数超过 n/k 的数」的前提条件。

当明确了符合要求的数的数量之后,我们可以使用有限变量来代表这 k1 个候选数及其出现次数。

然后使用「摩尔投票」的标准做法,在遍历数组时同时 check 这 k1 个数,假设当前遍历到的元素为 x

  • 如果 x 本身是候选者的话,则对其出现次数加一;
  • 如果 x 本身不是候选者,检查是否有候选者的出现次数为 0
    若有,则让 x 代替其成为候选者,并记录出现次数为 1
    若无,则让所有候选者的出现次数减一。
    当处理完整个数组后,这 k1 个数可能会被填满,但不一定都是符合出现次数超过 n/k 要求的。

需要进行二次遍历,来确定候选者是否符合要求,将符合要求的数加到答案。

上述做法正确性的关键是:若存在出现次数超过 n/k 的数,最后必然会成为这 k1 个候选者之一。

我们可以通过「反证法」来进行证明:若出现次数超过 n/k 的数 x 最终没有成为候选者。

有两种可能会导致这个结果:

数值 x 从来没成为过候选者:

如果 x 从来没成为过候选者,那么在遍历 x 的过程中,必然有 k1 个候选者被减了超过 n/k 次,假设当前 x 出现次数为 C,已知 C>n/k,此时总个数为

(k1)C+C=Ck

再根据 C>n/k,可知 Ck>n,而我们总共就只有 n 个数,因此该情况恒不成立。

数值 x 成为过候选者,但被逐出替换了:

同理,被逐出替换,说明发生了对 x 出现次数减一的动作(减到 0),每次的减一操作,意味着有其余的 k2 个候选者的出现次数也发生了减一动作,加上本身被遍历到的当前数 num[i],共有 k1 个数字的和 x 被一同统计。
因此,根据我们摩尔投票的处理过程,如果 x 成为过候选者,并被逐出替换,那么同样能够推导出我们存在超过 n 个数。

综上,如果存在出现次数超过 n/k 的数,其必然会成为 k1 个候选者之一。

这个算法的时间复杂度为 O(n/k)。同时暴力的 nlogn 保持不变,不失为一种可能的做法。

放在线段树内维护

需要指出的是,摩尔投票可以表示为一个二元组,是可以 O(1) 合并的信息,并且具有结合律。可以在线段树内维护。

更深刻的用法和性质

考虑区间众数的两个求法。

  1. 不确定候选众数。可以摩尔投票,得出最大数并且 cnt 确定 >len2,发现它是绝对众数。这个方法不可差分

  2. 确定候选众数 v。每个数,如果是 v 看成 1,否则看成 1。那么任意一个区间有绝对众数 v 当且仅当区间和 >0。这个可以差分为若干个前缀和

对于第二种,我们有性质:任何一个区间只有一个绝对众数,所以如果对所有候选众数做一遍,那么得到的所有有众数的区间不重复。

还有两个深刻的性质:

  1. 区间 [l,r] 有绝对众数 v,那么其分裂成任意两个区间 [l,mid] 以及 [mid+1,r],这两个区间满足一定存在其中一个有绝对众数 v

  2. [l,l],[l,l+1],...,[l,r] 这些区间,最多有 log2(rl+1) 个不同的众数。
    因为某个数要做绝对众数,一定比之前的所有数都多。于是出现次数 1,2,4,8,...

这两个性质组合起来可以有如下性质:

跨过 mid 的,在 [l,r] 内的所有区间的绝对众数最多有 log 种。这正是 cdq 分治的标准处理形式!

具体地,考虑 [mid,mid],[mid,mid+1],...,[mid,r][mid,mid],[mid1,mid],...,[l,mid] 中所有出现过的绝对众数,就是这些区间可能有的所有绝对众数。

然后你要批量处理这些区间的信息,可以这样:对于每一个可能的绝对众数,做第 2 种判定方式(前缀和),得到 sl,...,sr。然后分成两半 sl,...,smidsmid+1,...,sr,排序然后双指针归并,[l,r] 有绝对众数 v 当且仅当 sr>sl1

这样处理起来,时间复杂度:

T(n)=2T(n2)+O(nlog2n)

是三个 log 的,但是卡的非常不满,跑起来像一个 log 一样(笑,排序我可以改成桶,然后绝对众数卡不满这样就像 1log 了)

5e5 跑了 600ms(arc159_f)。

总结一下,这个方法帮助我们找到和处理所有含有绝对众数 v 的区间的方式,是很万能的,但是时间复杂度稍微难看点。

考虑最后一个过程,是给定一些 s,查询 sl<sr 的问题。这是一维偏序,值域是 O(len) 的,你可以认为 r 是查询,查询 <x 的数有几个。可以 O(len) 预处理答案,对于每一个 r 都是询问一下的事情。这样直接降到了 O(nlog2)

posted @   OIer某罗  阅读(601)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示