编程算法程序员

Boyer-Moore Majority Vote

2015-11-09  本文已影响1664人  曾会玩

问题定义

给定一个长度为 n的数组:
int[] nums
其中有一个数,它出现的次数大于n/2,称为主要元素,找到它。
看起来不算是个难题,但好玩。
这是一个投票问题,可以模拟我们在投票表决时的计票过程。用一个hash table或者dictionary,数组中的数作为key,它们出现的次数为value。这样的算法,时间和空间复杂度都是O(n)。
本文想讨论的是下边这些算法。

1.常见解法

1.1 排序

结论很简单:排序完之后,主要元素必然在下标n/2的位置。
看下面两个例子就很清楚了:

nums:    1,  1,  1,  2,  2
  i      0   1   2   3   4
n/2
=5/2
=2
nums[2]=1
主要元素是最小的数,排序后集中在最左边
nums:    1,  1,  2,  2,  2
  i      0   1   2   3   4
n/2
=5/2
=2
nums[2]=2
主要元素是最大的数,排序后集中在最右边

如果主要元素既不是最大的也不是最小的,那主要元素集中在中间一段,包括n/2
Python一句搞定:

def majorityElement(self, nums):
        return sorted(nums)[len(nums)/2]

分析:
元素是int型,没有限制更小的范围,基于比较的排序算法,最快O(nlogn)。

1.2 位操作

这里设int为32位整数。我们对这些数以二进制的形式,逐位观察,尝试构造出主要元素来。对32位中的每一位,如果1占多数,则主要元素的对应位为1,否则为0。

nums:  1,  2,  3,  3,  3
Binary:
  1:   0b0000....0001
  2:   0b0000....0010
  3:   0b0000....0011
  3:   0b0000....0011
  3:   0b0000....0011

major: 0b0000....0011

Java实现:

public int majorityElement(int[] nums) {
      int res=0,major=nums.length/2;
      for (int i=31;i>=0;i--){
          int pos=0;
          for(int n:nums)
              pos+=(n>>i)&1;
          pos=pos>major? 1:0;
          res|=pos<<i;
        }
      return res;
    }

分析:
时间复杂度为O(n),带个系数32,实际工作起来还是很快的。

2. Boyer-Moore算法

提出Boyer-Moore算法的论文
基本思想:
比较直观的解释:在数组中找到两个不相同的元素并删除它们,不断重复此过程,直到数组中元素都相同,那么剩下的元素就是主要元素。
思想并不复杂,但是要凭空想出这个算法来也不是件容易的事。另外,给我们的是数组,直接在里面删除元素是很费时的。取而代之,可以利用一个计数变量来实现。

def majorityElement(self, nums):
    count,major=0,0
    for n in nums:
        if count==0:
            major=n
        if major==n:
            count+=1
        else:
            count-=1
    return major

对于上面的代码:
先随意确定一个候选元素,count是候选元素的计数,当遇到一个跟候选元素不同的元素时,两者数量上抵消一个,count减1。一旦count变成0,就重新找一个候选元素。
当遇到一个与候选元素不同的元素时,就要抵消。对于候选元素和当前元素,可能存在两种情况:1)两者中有一个正好是主要元素;2)两者都不是主要元素。
对于情况1),抵消过后,主要元素还是主要元素;对于情况2),可以说主要的元素的地位得到了巩固。所以算法最终能找到主要元素。
One More Thing
上面的题目指出,满足条件的元素一定存在,那就可以直接返回我们找到的元素了。但事实上有时候这样的元素不一定存在,那么当我们找到这样一个元素时,还要进一步验证一下它是否满足条件。很简单,再遍历一遍,统计它的出现次数。

3. Generalization

如果题目是这样的:
找出 int[] nums中出现次数大于(不等于)n/3的元素,咋整。
解:首先可以明确的一点是,这样的元素可能有0个、1个、或者2个,再没有别的情况了。
然后,我们的Boyer-Moore算法思路,在这里依然可用,但需要些改动:
1)满足条件的元素最多有两个,那么需要两组变量。上面的count, major变成了count1, major1; count2, major2。
2)选出的两个元素,需要验证它们的出现次数是否真的满足条件。

def majorityElement(self, nums):
    candi1,candi2, count1,count2=0, 1, 0, 0
    for n in nums:
        if count1==0:
            candi1, count1=n, 0
        elif count2==0:
            candi2, count2=n, 0
        if n==candi1:
            count1+=1
        elif n==candi2:
            count2+=1
        else:
            count1-=1
            count2-=1
    #验证条件
    res=[n for n in set([candi1,candi2]) if nums.count(n)>len(nums)/3]
    return res
上一篇下一篇

猜你喜欢

热点阅读