Leetcode - 面试经典 150 题 - 多数元素
题目:多数元素
题目描述
给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:nums = [3,2,3]
输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2]
输出:2
提示:
n == nums.length
1 <= n <= 5 * 104
-109 <= nums[i] <= 109
输入保证数组中一定有一个多数元素。
题解
字典/哈希表
思路
遍历所有元素,将元素值作为键,元素出现次数作为值,只需要最后找到该记录中值最大的键值对中的键即可。下面的实现利用字典,也可以直接用内置的哈希表。
实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
records = {}
for n in nums:
if str(n) not in records:
records[str(n)] = 1
else:
records[str(n)] += 1
max_k, max_v = 0, 0
for k, v in records.items():
if v > max_v:
max_k = k
max_v = v
return int(max_k)
复杂度
-
时间复杂度:遍历一次数组,遍历一次字典,复杂度为 \(O(n)\);
-
空间复杂度:分配了一个新的字典空间,在一定存在多数元素的情况下,必定有至少
⌊ n/2 ⌋ + 1个元素相同,则最多分配n - ⌊ n/2 ⌋个键值对空间,因此复杂度为 \(O(n)\)。
排序(The best)
思路
由于多数元素的定义,可以推断出如果数组是有序的,则在 ⌊ n/2 ⌋ 处的元素一定是多数元素,因为有序数组中的前或者后 ⌊ n/2 ⌋ + 1 个元素一定是多数元素,因此不管哪种情况,nums[len(nums) // 2] 一定是多数元素。综上,只需要排序然后返回 nums[len(nums) // 2] 即可。
实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums) // 2]
复杂度
-
时间复杂度:取决于排序算法,由于使用了内置排序,此处复杂度为 \(O(nlogn)\);
-
空间复杂度:同上,复杂度为 \(O(logn)\)。
分治
思路
首先明确,数组的多数元素,一定是分解后所有子集中某个子集的多数元素:若数组被分解成 n 个子集,且其多数元素 \(a\) 不是这 n 个子集中的任意一个子集的多数元素,则一定有任意的第 i 个数组 \(a_i\) 中,\(a\) 的个数都少于 \(⌊ \frac{n_i}{2} ⌋\) ,加和后得出 \(a\) 的个数一定少于 ⌊ n/2 ⌋,与假设冲突,结论得证。
有了上述结论,我们可以将传入的数组分成两个部分,分别找出两边的多数元素,然后通过对比得到的两个元素哪个多即可。
实现
# 官方题解
class Solution:
def majorityElement(self, nums: List[int]) -> int:
def majority_element_rec(lo, hi) -> int:
if lo == hi:
return nums[lo]
mid = (hi - lo) // 2 + lo
left = majority_element_rec(lo, mid)
right = majority_element_rec(mid + 1, hi)
if left == right:
return left
left_count = sum(1 for i in range(lo, hi + 1) if nums[i] == left)
right_count = sum(1 for i in range(lo, hi + 1) if nums[i] == right)
return left if left_count > right_count else right
return majority_element_rec(0, len(nums) - 1)
复杂度
-
时间复杂度:\(O(nlogn)\),推导过程:

-
空间复杂度:开辟额外栈空间,复杂度为 \(O(logn)\)。
Boyer-Moore 投票算法
思路
利用多数元素性质,维护一个变量 count,遍历数组,若当前元素是多数元素则加1,否则减1,最终结果一定是大于0的。
算法步骤如下:
-
维护一个候选众数
candidate和它出现的次数count。初始时candidate可以为任意值,count为 \(0\); -
遍历数组的所有元素,对于每个元素
x,在判断x之前,如果count的值为 \(0\),先将x的值赋予candidate,随后判断x:-
x == candidate:count += 1; -
x != candidate:count -= 1。
-
-
遍历结束后,
candidate为众数(多数元素)。
以 count == 0 为界可以将一个数组分为多个子集,在每一段中,如果该段众数与整体众数相同,那么 count 的变化一定与真正的 count 相同,若不同则一定与之互为相反数,即记录的 count 与真正的 count 的关系只会使相等或者相反数,同时由于记录的 count 一定是非负的,则最后一段中记录的 count 与真正的 count 一定是相等的,反过来就可以推出此时 candidate 所维护的就是数组众数(多数元素)。
实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
count = 0
candidate = None
for num in nums:
if count == 0:
candidate = num
count += (1 if num == candidate else -1)
return candidate
复杂度
-
时间复杂度:只需要一次遍历,复杂度为 \(O(n)\);
-
空间复杂度:只需要维护两个变量,复杂度为 \(O(1)\)。

浙公网安备 33010602011771号