Majority Element浅谈 (A linear time iceberg query algorithm)

我们经常在面试中遇到这道题:
  给定一个数组nums, 找出可能的众数(即出现次数超过一半的数字)

这个题一种解法是配对比较的方法,即将其两两配对,如果相等则保留任意一个,如果不等则都舍去。如果存在众数,那最后剩下的一定是。至于是否存在众数,只需要将最后剩下的那个数遍历一遍数组,看出现次数是否超过一半即可。当然这种方法对于数组元素为奇数的情况要稍微处理下,因为没法两两配对,我们需要先检测一个数字是否是众数,如果是则返回,不是将其舍去化为偶数的情况。

另外一种解法是用Moore’s voting algorithm, 这个方法本质和上面的方法类似。但是写起来更简单,而且不用奇偶讨论,关键的是这是一个在线算法,即它不仅可以处理数组,即使输入是一个不知道长度的流(stream),也可以在读完流的时候返回众数(当然前提是众数必须存在,如果不确定是否存在则需要将流遍历两遍,第一遍找出潜在的众数,第二遍检测这个潜在的众数是不是真的众数)。具体方法为:

   我们用一个数a记录当前潜在的众数, times记录次数初始为0,然后从左到右遍历数组,如果nums[i]和a相等,则times++(类似于第一种算法相等保留任意一个); 不等则times– (类似于不等则都舍去一个),并且times = 0 时将 a = nums[i]且times = 1。当遍历到数组末尾是,如果数组中存在众数,则一定是a,最后只需要再遍历一遍数组确认一下即可。

显然第二个算法本质上和第一个没啥区别,但是更优美,两个问题都是O(n)时间, O(1)空间。

不过现在我们有一个新问题,即如果不仅仅是找众数,而是:
  给定一个流或者数组,一个在0与1之间的实数 θ ,能否找出其中出现频率超过 θ 的全部元素

这里 θ 0.5 就是我们前面一个问题。

这个问题看似比较难,其实被Karp老爷子研究过,他在基于上面Moore’s voting的方法上做了一些改进,写了5页的小论文《A Simple Algorithm for Finding Frequent Elements in Streams and Bags》 有兴趣的同学可以去看看,不过这里就直接给出论文中重要的结果吧。

这个问题可以在两次遍历这个流,在 O(1/θ) 的空间内解决。具体来说如下:

//设X[1...n]为输入,是一个int流 
//我们把出现频率超过1/theta的数字叫做高频数
map<int,int > K; //K中保存潜在的高频数 K[a] = b 表示遍历到当前时数字a还剩b个

//第一遍遍历这个流,找出潜在的高频数
for(int i = 1; i <= n; ++i){
    //先将X[i]加到K中
    if( K.find(X[i]) != K.end() )
        K[X[i]] ++;
    else 
        K[X[i]] = 1;

    //如果K中元素超过 1/theta, 则K中元素每个都被舍弃一次
    if(K.size() > 1/theta){
        for(auto it = m.begin(); it != m.end();){
            it->second --;
            if(it->second == 0)
                m.erase(it++);
            else
                it++;
        }
    }
}

// 此时所有高频数肯定在K里面,这时再遍历一遍确认一下就可,这个过程的代码略

显然算法非常简洁,但关键是如何证明第一遍遍历后需要找的高频数字一定在K里面呢? 用反证法即可, 假设a是高频数, 但a最后不在K中。由于遍历到a时,a一定被加入到K中了,因此唯一的可能性是它后来又被舍弃了。注意到,我们在舍弃过程中,是遍历K中元素,每个都舍弃了一次(代码中it->second –), 由于此时K至少有 1/θ 个元素,因此我们至少舍弃了 1/θ 次。假设a在数组中出现了 t 次,则每次a被加入后最后都被舍弃,说明我们至少舍弃了t1/θ次,这个次数一定不能超过输入流元素的总数N。故
   t1/θ<=N t/N<=θ ,这与a是高频数的定义矛盾。反证法结束!

这样算法的正确性就得到证明。最后显然算法在两遍遍历和很小的空间(假设 θ 是个常数)中解决。这个算法有些地方也叫作A linear time iceberg query algorithm, 到这里我们认为比较圆满的解决了该问题。有兴趣的同学可以去解决leetcode上的Majority Element II .

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值