相关题目
摩尔投票法
摩尔投票法基于这样一个事实,当一个数的重复次数超过数组长度的一半,每次将两个不相同的数删除,最终剩下的就是要找的数。
为了解释清楚这个问题,首先来看leetcode的第169题。
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入: [3,2,3]
输出: 3
示例 2:
输入: [2,2,1,1,1,2,2]
输出: 2
解决这个题的思路是这样的:
我们维护一个计数器,如果遇到一个我们目前的候选众数,就将计数器加一,否则减一。只要计数器等于 0 ,我们就将 nums 中之前访问的数字全部 忘记 ,并把下一个数字当做候选的众数。这样到最后,总能找到符合要求的数。
为什么呢?考虑这个问题:当我们“减到0重新换个数开始计数”,实际上我们从原数组中忽略了2n个数,其中有n个是减到零之前那个候选的数,另外n个数不知道是什么数,反正不是那个候选的数。也就是说,可以看作删掉了n组元素,每组元素包括两个元素,这两个元素是不同的。
这一过程可以看作“抵消”。什么意思呢,最差情况下,每一对中都有一个众数,那么一个众数抵消一个非众数,由于本题中的众数是大于出现次数一半的,所以抵消之后,剩下的数组中,众数的值不受影响。因此,到最后还剩一个或几个数,一定是众数。
class Solution:
def majorityElement(self, nums: List[int]) -int:
count = 0
for num in nums:
if count == 0:
candidate = num
count = count + 1 if candidate == num else count - 1
return candidate
到这里还比较好理解。下面是一个扩展:leetcode第229题。
给定一个大小为 n 的数组,找出其中所有出现超过 ⌊ n/3 ⌋ 次的元素。 说明: 要求算法的时间复杂度为 O(n),空间复杂度为 O(1)。
示例 1:
输入: [3,2,3]
输出: [3]
示例 2:
输入: [1,1,1,3,3,2,2,2]
输出: [1,2]
这个题的解决思路是这样的:
超过n/3的数最多只能有两个。先选出两个候选人A,B。 遍历数组,分三种情况:
1.如果投A(当前元素等于A),则A的票数++;
2.如果投B(当前元素等于B),B的票数++;
3.如果A,B都不投(即当前与A,B都不相等),那么检查此时A或B的票数是否减为0。
评论区里说这样可以选出来第一多和第二多的元素,但是根据我的实验并非如此,第二多的元素不一定会被选出来,思考了很久之后,借鉴上面的想法,我觉得应该是这样的:
我们有两个计数器,count1和count2,当遍历到的元素等于num1时,count1自增1,而当遍历到的元素不等于num1也不等于num2时,count1才自减1。这说明,当count1减到零的时候,我们选择遗忘num1,这实际上也是抵消了n组元素,每组元素包括3个元素,这三个元素也是不同的(num1,num2,另一个不同的数)。
那么,和之前的思路相同,假设有一个数出现的次数大于n/3,它一定会留到最后。那么留到最后的数是否一定出现的次数大于n/3呢?或者是否一定是第一第二大的数呢?非也。如果并不存在这样符合要求的数,那么最后会留下什么数就是天知道。实际上上一道题也是一样,不过上一题的题干里保证了有这样的数,否则一样要验证。
class Solution:
def majorityElement(self, nums: List[int]) -> List[int]:
count1,count2 = 0,0
num1,num2 = None,None
for num in nums:
if num == num1:
count1 += 1
elif num == num2:
count2 += 1
elif count1 == 0:
num1 = num
count1 += 1
elif count2 == 0:
num2 = num
count2 += 1
else:
count1 -= 1
count2 -= 1
count1,count2 = 0,0
for num in nums:
if num == num1: count1 += 1
if num == num2: count2 += 1
return [i[0] for i in list(zip([num1,num2],[count1,count2])) if i[1] > len(nums) // 3]