摩尔投票法和大多数

我的原文:http://www.hijerry.cn/p/45987.html

摩尔投票算法

假设有这样一个场景:票选村长,每人可投一票,我们将候选村长从1开始编号,村民们在票上写上候选村长的编号即可完成投票。那么最后统计的票可形成一个整型数组。那么谁是村长呢?票数过半的那个人。

摩尔投票算法可以快速的计算出一个数组中出现次数过半的数即大多数(majority),算法核心思想是同加,异减。我们举个例子。

假设数组是:[1,2,1,1,2,1]。算法步骤如下:

  • 1。当前大多数是1,得分置1
  • 2。与当前大多数不同,得分 - 1,得分为0,当前大多数 = 1
  • 1。与当前大多数不同,得分为0,所以设置当前大多数 1 -> 1,得分置1
  • 1。与当前大多数相同,得分 + 1,得分为2,当前大多数 = 1
  • 2。与当前大多数不同,得分 - 1 ,得分为1,当前大多数 = 1
  • 1。与当前大多数相同,得分 + 1,得分为2,当前大多数 = 1

这意味着1是这个数组中出现次数过半的数。

可以感受得到,算法会保存一个当前大多数,和得分,当遇到一个数不是当前大多数时,得分会减一,当减到0时,大多数会发生改变,并且重置得分为1。

这里需要区分的是,摩尔算法不能用来得到众数(mode),例如数组:[1,1,1,2,2,3,3,4,4],摩尔算法得出最后的结果应该是4,但4并不是众数,可是显然4也不是大多数,那是因为,大多数是指出现次数过半的数,而这个数组中没有这样的数,所以摩尔算法是是失效的,对于这种情况采取需要重新投票。

出现次数超过一半的数

LeetCode原题:169. Majority Element

这里要求出现次数大于一半,所以直接套用摩尔投票算法即可得到答案。

class Solution(object):
    def majorityElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        a, ca = None, 0
        for n in nums:
            if   a == n : ca += 1
            elif ca == 0: a, ca = n, 1
            else        : ca -= 1
        
        return a

出现次数超过数组1/3长

LeetCode原题:229. Majority Element II

还能用摩尔投票法吗?答案当然是要,但是需要变通一下。

需要注意的是出现次数超过1/3数组长的数,也许会有多个,例子如下:

  • [1,1,1,2,2,2,3,3],数组1/3长=2(向下取整),所以1和2都是符合条件的。

但最多只能是2个,证明如下:

证明

假设数组有且只有3个数a,b,c,它出现的次数分别为ca, cb ,cc ,不妨假设 c a = c b = ⌊ n 3 ⌋ + 1 ca = cb= ⌊\frac n 3⌋ + 1 ca=cb=3n+1,数组长度为 n n n

n = 3 a + b n = 3a + b n=3a+b,其中 a 、 b a、b ab均为非负整数,其实这就是余数公式,其中 b b b 是余数, a = ⌊ n 3 ⌋ a = ⌊\frac n 3⌋ a=3n,那么 n 3 = a + b 3 \frac n 3 = a + \frac b 3 3n=a+3b,即 n 3 − b 3 = ⌊ n 3 ⌋ \frac n 3 - \frac b 3 = ⌊\frac n 3⌋ 3n3b=3n

所以:

c a + c b + c c = n ca + cb + cc = n ca+cb+cc=n

c a + c b = 2 ∗ ( n 3 − b 3 ) + 2 = 2 n 3 − 2 b 3 + 2 ca + cb = 2 * (\frac n 3 - \frac b 3) + 2 = \frac {2n} 3 - \frac {2b} 3 + 2 ca+cb=2(3n3b)+2=32n32b+2

由①、②整理得 c c = n + 2 b − 6 3 cc = \frac {n + 2b - 6} 3 cc=3n+2b6 ,做差:

c c − ⌊ n 3 ⌋ = n + 2 b − 6 3 − n 3 + b 3 = b − 2 cc - ⌊\frac n 3⌋ = \frac {n + 2b - 6} 3 - \frac n 3 + \frac b 3 = b - 2 cc3n=3n+2b63n+3b=b2

因为 b = n % 3 b = n \% 3 b=n%3,所以 b − 2 ≤ 0 b - 2 \le 0 b20 恒成立,所以 c c ≤ ⌊ n 3 ⌋ cc \le ⌊\frac n 3⌋ cc3n

可以知道,如果连 c a = c b = ⌊ n 3 ⌋ + 1 ca = cb= ⌊\frac n 3⌋ + 1 ca=cb=3n+1 时都找不到一个 c c cc cc 使得它大于 ⌊ n 3 ⌋ ⌊\frac n 3⌋ 3n,那么取ca、cb为其他比 ⌊ n 3 ⌋ ⌊\frac n 3⌋ 3n 大的数,更加不可能找到符合条件的 c c cc cc

综上,一个数组中不可能存在2个以上的数它们出现的次数大于 ⌊ n 3 ⌋ ⌊\frac n 3⌋ 3n

回到题目

如果我们在使用摩尔算法时,同时记录两个大多数,会怎么样呢?直觉告诉我,这会得到一个大多数,和一个出现次数仅次于大多数的数,但是这两个数不一定会比数组长的1/3大

所以我们得到它们后,还需要检查它们出现的次数是否符合条件。

AC代码:

class Solution(object):
    def majorityElement(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        a, b, ca, cb, ans = None, None, 0, 0, []
        for n in nums:
            if   n  == a: ca += 1
            elif n  == b: cb += 1
            elif ca == 0: a, ca = n, 1
            elif cb == 0: b, cb = n, 1
            else:         ca, cb = ca - 1, cb - 1
        ca, cb = 0, 0
        for n in nums:
            if   n == a: ca += 1
            elif n == b: cb += 1
        
        if ca > len(nums)/3:
            ans.append(a)
        if cb > len(nums)/3:
            ans.append(b)
        return ans

你可能感兴趣的:(数据结构/算法)