题目难度:简单
默认优化目标:最小化平均时间复杂度。
Python默认为Python3。
目录
1 题目描述
给定一个大小为 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
进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。
2 题目解析
这个题目描述十分简洁,没有什么弯弯绕的地方。
输入是长度为n
的数组nums
,输出是出现次数大于[n/2]
的元素。
输出只可能是一个元素,因为出现次数大于[n/2],假设存在2个及以上的元素满足条件,数组的超度就超过n
了,与已知条件不符。
最先想到的是暴力求解,即用一个计数器去统计元素出现的次数。那这样平均时间复杂度为O(n^2)。对于面试,这样写出来等于没写,因为人人都会这种方法且这种方法效果太差。那我们就需要用别的方法来降低时间复杂度。
3 算法原理及程序实现
3.1 暴力求解(不推荐)
平均时间复杂度,平均空间复杂度。会超出力扣的运行时间限制。
C++代码实现
class Solution {
public:
int majorityElement(vector<int>& nums) {
int n = nums.size();
for (int i = 0; i < n; i++) {
int count = 0;//判断每个元素出现的次数前,计数器先清零
for (int j = i; j < n; j++) {//相同的元素在前面位置的,前面能满足条件则满足,满足不了后面也满足不了
if (nums[j] == nums[i]) {
count++;
}
}
if (count > n / 2) {
return nums[i];//满足条件输出,程序结束
}
}
return -1;
}
};
Python代码实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
n=len(nums)
for i in range(0,n):
count=0
for j in range(i,n):
if nums[i]==nums[j]:
count+=1
if count>n/2:
return nums[i]
return -1
3.2 排序
我们注意到,nums
中出现次数大于[n/2]
的元素,如果经过排序后,则该元素必然在nums[n/2]
。
如果使用快排,则平均时间复杂度为,平均空间复杂度为。
C++代码实现
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
return nums[nums.size()/2];
}
};
Python代码实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums.sort()
return nums[len(nums) // 2]
3.3 哈希表
我们可以使用哈希表来快速统计每个元素出现的次数,通过返回值最大的所对应的键,即可找到出现次数大于n/2
的元素。
平均时间复杂度,平均空间复杂度。
C++程序实现
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int>counts;//创建一个哈希表<key,value>,键值都是int型
int majority=0,cnt=0;//majority用于记录出现次数大于n/2的元素,cnt用于记录次数
for(int num: nums){//引用方式,通过键遍历哈希表
++counts[num];
if(counts[num]>cnt){//如果元素的出现次数大于当前最大的出现次数,更新键值
majority=num;
cnt=counts[num];
}
}
return majority;
}
};
Python代码实现
class Solution:
def majorityElement(self, nums: List[int]) -> int:
counts=collections.Counter(nums)#计数可哈希对象的出现次数并创建一个字典
return max(counts.keys(),key=counts.get)#获取每个键的出现次数,返回出现次数最多的那个键的值,即所求输出元素
3.4 分治
原理:如果a
是nums
的众数,则将nums
分成两部分后,a
必是至少一部分的众数。
假设n
为偶数,如果a
不是任意两部分的众数,临界情况是a
和b
都出现n/2
次且nums
从中间均分,两部分a
和b
的数量各占一半。但a出现次数大于n/2
,所以原理成立。n
为奇数同理,因为a的出现次数大于n/2
,原理成立。
为了使用递归,我们每次将数组从中间分成左右两部分,然后左数组和右数组再分成左右两部分,直到数组的长度为1,这样这个数必然是众数。然后回溯区间,判断这个众数是不是这个区间的众数。是则是,否则我们需要比较这两个众数在整个区间内出现的次数来决定该区间的众数。
平均时间复杂度为,平均空间复杂度为。
对这部分的时间复杂度求取见附录。
C++代码实现。
class Solution {
int count_in_range(vector<int>& nums, int target, int lo, int hi) {//计算一段区间内的众数
int count = 0;
for (int i = lo; i <= hi; ++i)
if (nums[i] == target)
++count;
return count;
}
int majority_element_rec(vector<int>& nums, int lo, int hi) {
if (lo == hi)//如果长度为1,直接返回
return nums[lo];
int mid = (lo + hi) / 2;//分治
int left_majority = majority_element_rec(nums, lo, mid);//[lo,mid],递归
int right_majority = majority_element_rec(nums, mid + 1, hi);//[mid+1,hi],递归
if (count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) / 2)//左边众数出现次数多于右边,返回左边
return left_majority;
if (count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) / 2)//右边众数出现次数多于左边,返回右边
return right_majority;
return -1;
}
public:
int majorityElement(vector<int>& nums) {
return majority_element_rec(nums, 0, nums.size() - 1);
}
};
Python代码实现
class Solution:
def count_in_range(self, nums, target, lo, hi)->int:
count = 0
for i in range(lo, hi + 1):
if nums[i] == target:
count += 1
return count
def majority_element_rec(self, nums, lo, hi)->int:
if lo == hi: # 如果长度为1,直接返回
return nums[lo]
mid = (lo + hi) // 2 # 分治
left_majority = self.majority_element_rec(nums, lo, mid) # [lo, mid],递归
right_majority = self.majority_element_rec(nums, mid + 1, hi) # [mid+1, hi],递归
if self.count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) // 2: # 左边众数出现次数多于右边,返回左边
return left_majority
if self.count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) // 2: # 右边众数出现次数多于左边,返回右边
return right_majority
return -1
def majorityElement(self, nums: List[int])->int:
return self.majority_element_rec(nums, 0, len(nums) - 1)
3.5 Boyer-Moore投票算法
上帝视角:我们将nums
中的元素依次和真正的众数mode
去比,是+1
,否-1
,用val
来记录。
平民视角:设置一个计数器。首先我们将nums
的第一个元素设置成众数,依次和后面的数去比较。和此众数相同则+1
,不同则-1
。当count=0
时,记下一个元素为众数。重复此步骤。
示例如下:
我们可以发现,count
要么和val
相等,要么相反。因此我们可以用过count
来找到众数。count
为负时我们将其取正即可,即当count=-1
,令count=1
。
平均时间复杂度为,平均空间复杂度为。
C++代码实现
class Solution {
public:
int majorityElement(vector<int>& nums) {
int mode = -1;
int count = 0;
for (int num : nums) {
if (num == mode)
++count;
else if (--count < 0) {//否则count自减1并且进行是否为0判断
mode = num;
count = 1;//相当于变成val
}
}
return mode;
}
};
Python代码实现
def majorityElement(self, nums: List[int]) -> int:
mode,count=None,0
for num in nums:
if count==0:
mode=num
count+=(1 if num==mode else -1)
return mode
3.6 随机法
因为出现次数大于n/2
,我们随机挑选一个数,这个数为众数的可能性很大。我们检验我们挑的数是否为众数,是输出,否重复上述步骤。
平均时间复杂度为,平均空间复杂度为。
C++实现代码
class Solution {
public:
int majorityElement(vector<int>& nums) {
while (true) {
int mode = nums[rand() % nums.size()];
int count = 0;
for (int num : nums)
if (num == mode)
++count;
if (count > nums.size() / 2)
return mode;
}
return -1;
}
};
Pyhon实现代码
class Solution:
def majorityElement(self, nums: List[int]) -> int:
majority_count = len(nums) // 2
while True:
mode = random.choice(nums)
if sum(1 for elem in nums if elem == mode) > majority_count:
return mode
附录 分治法的时间复杂度求取
主定理用于解决分治法递归关系式,适用形式
用下面的法则来确定复杂度
情况1:
情况2:
情况3:
本文中的分治法为分解成2
个长度为n/2
的子问题,做了两遍长度为n
的线性扫描(第一遍为n/2+n/2=n,第二遍为n/4+n/4=n/2,第三遍为n/4,... ,第k遍为n/2^k,用等比数列求和公式得到2n),故
因此
所以适用情况2,故时间复杂度为