适用范围
摩尔投票法是用来寻找数组中的多数元素(主要元素)。而多数元素的含义就是出现次数超过数组个数一半的元素(经典摩尔投票法,也可以对其改进来求数量超过总数几分之几的元素),注意一定是元素出现次数超过一半,刚好等于一半是不能使用这种算法的。下面是对于摩尔投票法形象化的描述:
想象着这样一个画面:会议大厅站满了投票代表,每个都有一个牌子上面写着自己所选的候选人的名字。然后选举意见不合的(所选的候选人不同)两个人,会打一架,并且会同时击倒对方。显而易见,如果一个人拥有的选票比其它所有人加起来的选票还要多的话,这个候选人将会赢得这场“战争”,当混乱结束,最后剩下的那个代表(可能会有多个)将会来自多数人所站的阵营。但是如果所有参加候选人的选票都不是大多数(选票都未超过一半),那么最后站在那的代表(一个人)并不能代表所有的选票的大多数。因此,当某人站到最后时,需要统计他所选的候选人的选票是否超过一半(包括倒下的),来判断选票结果是否有效。
如果觉得不好理解的话可以看看这个动画,非常的形象:
v2-8f7078d30a515fc3b4a8308a6dd99550_b.webp (508×714) (zhimg.com)
(引用自:摩尔投票法(Boyer–Moore majority vote algorithm) - 知乎 (zhihu.com))
直接实战
题目一、
我们看力扣的题目,求数组的主要元素,其实就是考查摩尔投票法的使用(当然哈希表也可以)
那么我们根据摩尔投票法做题
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
}
题目二、
再看一道求众数的题
这里让我们求的是超过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
}