LeetCode 169. 多数元素【简单】

84 阅读4分钟

题干

给定一个大小为 n **的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。 你可以假设数组是非空的,并且给定的数组总是存在多数元素。

进阶: 尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

题解

  • 想法一:用一个额外的map去存储数组中每一个元素出现的次数,当一个元素出现的次数最多时,那个数就是众数。这样只需要遍历一次就可以得到结果,时间复杂度为O(n),空间复杂度为O(n)。
  • 想法二:将数组排序之后,数组中间位置上的那个元素一定是众数。主要的开销在于排序,因此时间复杂度和空间复杂度和所使用的排序算法一致。

这里另外给出两种我没有想到的方法

分治法

分治法的精髓就是将一个复杂的问题,拆解成简单的子问题进行求解,分而治之,本题也是一样。将整个数组分为两个部分,整个数组的众数为左半边的众数和右半边的众数中出现次数更多的那个数,根据这个想法很容易写出递归的代码,递归的出口就是当数组中只有一个数时,那个数就是众数。

  • 算法时间复杂度 O(nlogn)
  • 空间复杂度 O(logn)
// 分治法
// 统计一个数在数组内出现的次数
func getMajorityCount(majority int, left int, right int, nums []int) int {
	cnt := 0
	for i := left; i <= right; i++ {
		if majority == nums[i] {
			cnt++
		}
	}
	return cnt
}

func findMajorityElement(left int, right int, nums []int) int {
	// 递归出口:当数组中只有一个数,那个数就是众数
        if left == right {
		return nums[left]
	}
        // 将整个数组分为两个部分,分别求解众数
	mid := (left + right) / 2
        // 递归分别选出左半边数组和右半边数组的众数
	var leftMajority = findMajorityElement(left, mid, nums)
	var rightMajority = findMajorityElement(mid+1, right, nums)
        // 分别统计出左半边众数和右半边众数在整个数组中出现次数,选择出现次数更多的那个数为众数
	if getMajorityCount(leftMajority, left, right, nums) < getMajorityCount(rightMajority, left, right, nums) {
		return rightMajority
	} else {
		return leftMajority
	}
}

func majorityElement(nums []int) int {
	// 分治法
	return findMajorityElement(0, len(nums)-1, nums)
}

摩尔投票法(Boyer Moore投票法)

包括分治法在内的上面三种算法,都没有办法使得空间复杂度为O(1)。在选择任意多的无序候选人中得票最多的那个候选人时有一个非常巧妙的算法,它就是摩尔投票法。考虑这道题目,数组中一定存在一个数为众数,我们假设这个数为a,数组中其他的数的取值对于”a为众数“这个议题做出了投票,如果说某个数等于a,那么就相当于投出了肯定票;反之,就投出了否决票。当肯定票和否决票平票的时候,我们可以认为肯定票和否决票”抵消“了,这些票就被”废弃“了,对最终谁是众数这个议题没有任何的影响。
根据上面的想法,我们可以得出下面的算法:

  • 在初始状态下,我们就取数组的第一个数为候选众数,称这个数为candidate
  • 遍历数组,在遍历的过程中,使用一个变量cnt来记录”当前candidate为众数“这个议题的投票结果。如果,当前数等于candidate,那么cnt++,即,投出肯定票;反之,cnt--,即投出否定票。
  • cnt为0,则代表出现了之前遍历的数对"当前candidate为众数"这个议题平票了,这个时候,可以直接丢弃之前的投票结果,重新选取当前的数为新的candidate,并继续向后遍历,也即对新的议题发起投票。

  • 算法时间复杂度 O(n)
  • 空间复杂度 O(1)
func majorityElement(nums []int) int {
	// Boyer-Moore 投票算法
	var candidate int
	var cnt int

	for _, num := range nums {
		// 如果cnt为0,说明当前没有候选的众数,因此把当前数作为众数,并且投出一票
		if cnt == 0 {
			candidate = num
			cnt++
			continue
		}
		// 如果当前数不等于当前候选众数,相当于投出相反的一票,将cnt-1;如果等于当前候选众数,则投出
		if num != candidate {
			cnt--
		} else {
			cnt++
		}
	}
	return candidate
}