[算法] 摩尔投票法

330 阅读3分钟

适用范围

摩尔投票法是用来寻找数组中的多数元素(主要元素)。而多数元素的含义就是出现次数超过数组个数一半的元素(经典摩尔投票法,也可以对其改进来求数量超过总数几分之几的元素),注意一定是元素出现次数超过一半,刚好等于一半是不能使用这种算法的。下面是对于摩尔投票法形象化的描述:

想象着这样一个画面:会议大厅站满了投票代表,每个都有一个牌子上面写着自己所选的候选人的名字。然后选举意见不合的(所选的候选人不同)两个人,会打一架,并且会同时击倒对方。显而易见,如果一个人拥有的选票比其它所有人加起来的选票还要多的话,这个候选人将会赢得这场“战争”,当混乱结束,最后剩下的那个代表(可能会有多个)将会来自多数人所站的阵营。但是如果所有参加候选人的选票都不是大多数(选票都未超过一半),那么最后站在那的代表(一个人)并不能代表所有的选票的大多数。因此,当某人站到最后时,需要统计他所选的候选人的选票是否超过一半(包括倒下的),来判断选票结果是否有效。

如果觉得不好理解的话可以看看这个动画,非常的形象:

v2-8f7078d30a515fc3b4a8308a6dd99550_b.webp (508×714) (zhimg.com)

(引用自:摩尔投票法(Boyer–Moore majority vote algorithm) - 知乎 (zhihu.com)

直接实战

题目一、

我们看力扣的题目,求数组的主要元素,其实就是考查摩尔投票法的使用(当然哈希表也可以) image.png

那么我们根据摩尔投票法做题

func majorityElement(nums []int) int {
    digit, result := 0, 0
    //互相抵消阶段
    for _, v := range nums {
        if digit == 0 {  //当标识为0时,记录当前元素值
            result = v
            digit = 1
        } else {        //当标识不为0时,判断当前元素与记录元素是否相等
            if v == result {
                digit++
            } else {
                digit--
            }
        }
    }
    digit = 0
    //通过上面获取到目标元素的值,但是还要对其个数进行判断
    //因为我们不知道目标元素的个数是否超过一半
    for _, v := range nums {
        if v == result {    
            digit++
        }
    }
    if digit <= len(nums)/2 {
        result = -1
    }
    return result
}
题目二、

再看一道求众数的题

image.png

这里让我们求的是超过n/3次的元素,而经典的摩尔投票法求得是超过n/2次的元素,那么就需要稍微改进一下,可以说是:在任何数组中,出现次数大于该数组长度1/3的值最多只有两个。那么这道题求次数超过n/3的众数也就是找到第一多和第二多的元素即可

func majorityElement(nums []int) []int {
    //因为我们要找第一多和第二多两个数,因此我们需要定义两组标识量
    //m1,count1接收第一多的数据,m2,count2接收第二多的数据
    m1, m2 := math.MaxInt, math.MaxInt
    count1, count2 := 0, 0 
    for _, v := range nums {
        if count1 > 0 && v == m1 {  //元素相同则++
            count1++
        } else if count2 > 0 && v == m2 {
            count2++
        } else if count1 == 0 {  //当标识位为0,说明m1记录的元素被抵消完毕,此时要重新记录元素
            m1 = v
            count1++
        } else if count2 == 0 { //当标识位为0,说明m2记录的元素被抵消完毕,重新记录第二多的元素
            m2 = v
            count2++
        } else {
            count1--    //当count1>0并且v!=m1时,进行抵消操作,也就是进行count--
            count2--    //同理
        }
    }
    
    //到这里,m1,m2记录的就是第一多和第二多的元素
    //但是我们还要对其数量进行校验,判断是否超过n/3
    count1, count2 = 0, 0
    for _, v := range nums {
        if v == m1 {
            count1++
        } else if v == m2 {
            count2++
        }
    }
    result := []int{}
    if count1 > len(nums)/3 {
        result = append(result, m1)
    }
    if count2 > len(nums)/3 {
        result = append(result, m2)
    }
    return result
}