力扣解题-169. 多数元素
给定一个大小为 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
输入保证数组中一定有一个多数元素。
第一种解答
解题思路
核心方法:哈希表统计频次,通过HashMap记录每个元素的出现次数,遍历过程中检查是否达到“超过n/2”的条件,逻辑直观但时间/空间效率一般。
具体步骤:
- 边界处理:若数组长度<2,直接返回唯一元素(必然是多数元素);
- 初始化变量:
halfLength = nums.length/2:多数元素的频次阈值;map:HashMap<Integer, Integer>,键为数组元素,值为对应出现次数;much:存储最终找到的多数元素,初始为0。
- 遍历数组统计频次:
- 遍历每个元素
i,若map中已包含该元素,将计数+1;若计数超过halfLength,立即记录该元素为much; - 若
map中未包含该元素,初始化计数为1。
- 遍历每个元素
- 返回结果:遍历完成后返回
much。
性能劣势说明
该解法耗时18ms仅击败5.37%用户,核心问题是:
- 时间复杂度的额外开销:哈希表的
containsKey、get、put操作存在哈希冲突的可能(最坏O(n)),且遍历过程中需频繁更新/查询哈希表,总时间复杂度为O(n)但常数因子较大; - 空间复杂度非最优:哈希表需存储所有不同元素,空间复杂度O(n),违背“最优解O(1)空间”的要求;
- 内存表现较好(51.7MB击败87.29%):仅使用基础哈希表,无额外冗余存储,属于该思路下的正常表现。
public int majorityElement(int[] nums) {
int much=0;
int length=nums.length;
if(length<2){
return nums[0];
}
int halfLenght=length/2;
Map<Integer, Integer> map = new HashMap<>();
for(int i : nums){
if(map.containsKey(i)){
Integer count=map.get(i)+1;
if(count>halfLenght){
much=i;
}
map.put(i,count);
}else {
map.put(i, 1);
}
}
return much;
}
第二种解答
解题思路
核心方法:排序 + 遍历计数,先对数组排序(重复元素连续),再遍历统计元素出现次数,对比当前最多计数,逻辑冗余且性能较差。
具体步骤:
- 边界处理:数组长度<2时直接返回唯一元素;
- 排序预处理:调用
Arrays.sort(nums),使重复元素连续排列; - 初始化计数变量:
currentElement(当前统计元素)、currentNum(当前计数)、nextElement(新元素)、nextCountNum(新元素计数); - 遍历统计次数:
- 第一个元素初始化
currentElement和currentNum; - 后续元素若与
currentElement相同则计数+1,若超过n/2则终止遍历; - 若为新元素则初始化
nextCountNum,并对比是否超过当前最大计数,更新currentElement;
- 第一个元素初始化
- 返回结果:返回最终的
currentElement。
性能劣势说明
该解法耗时6ms击败27.13%用户,核心问题是:
- 排序的额外开销:排序时间复杂度为O(nlogn),远高于最优解的O(n);
- 计数逻辑冗余:排序后重复元素已连续,无需维护
nextElement和nextCountNum,只需单变量计数即可,多余的变量和判断增加了时间常数; - 内存表现一般(54.8MB击败34.66%):排序的栈空间开销+多变量维护,导致内存占用上升。
public int majorityElement(int[] nums) {
if(nums.length<2){
return nums[0];
}
//先将元素进行排序
Arrays.sort(nums);
int currentElement = 0;
int currentNum = 0;
int nextElement = 0;
int nextCountNum = 0;
int halfLength=nums.length/2;
for(int i=0;i<nums.length;i++){
int num=nums[i];
if(i==0){
currentElement = num;
currentNum++;
}else{
if(currentElement==num){
currentNum++;
if(currentNum>halfLength){
break;
}
}else{
nextElement = num;
nextCountNum++;
if(nextCountNum>currentNum){
currentElement = num;
currentNum=nextCountNum;
}
if(nextCountNum>halfLength){
break;
}
}
}
}
return currentElement;
}
第三种解答
解题思路
核心方法:排序 + 取中间值,利用“多数元素出现次数超过n/2”的特性,排序后数组的中间位置(n/2)必然是多数元素,简化了计数逻辑,性能优于前两种但仍有排序开销。
核心原理铺垫
多数元素出现次数 > ⌊n/2⌋ → 排序后该元素必然覆盖数组的中间位置:
- 例如n=7(⌊7/2⌋=3),多数元素至少出现4次,排序后第3位(索引3)必然是该元素;
- 例如n=6(⌊6/2⌋=3),多数元素至少出现4次,排序后第3位(索引3)必然是该元素。
具体步骤:
- 边界处理:数组长度<2时直接返回唯一元素;
- 排序数组:
Arrays.sort(nums)使元素有序; - 取中间值:计算索引
index = nums.length/2,返回nums[index]。
性能说明
该解法耗时5ms击败39.34%用户,相比前两种有优化但仍有短板:
- 时间复杂度仍为O(nlogn):排序的开销是核心瓶颈,虽简化了后续逻辑,但无法达到最优的O(n);
- 空间复杂度:排序的栈空间开销(O(logn)),高于最优解的O(1);
- 内存表现差(55MB击败5.14%):排序的临时空间+数组拷贝开销导致内存占用偏高。
public int majorityElement(int[] nums) {
if(nums.length<2){
return nums[0];
}
Arrays.sort(nums);
int index=nums.length/2;
return nums[index];
}
示例解答
解题思路
核心方法:摩尔投票法(Boyer-Moore 投票算法),利用“多数元素出现次数超过n/2”的特性,通过投票抵消的方式找到候选元素,时间复杂度O(n)、空间复杂度O(1),是本题的最优解法。
核心原理铺垫
摩尔投票法的核心逻辑:
- 若存在多数元素,其出现次数超过所有其他元素的总和;
- 遍历数组时,维护一个候选元素
candidate和计数count:- 当
count=0时,将当前元素设为候选; - 若当前元素等于候选,
count++(投票支持); - 若当前元素不等于候选,
count--(投票抵消);
- 当
- 最终剩下的候选元素必然是多数元素(题目保证存在多数元素)。
具体步骤:
- 初始化变量:
count=0:投票计数,初始为0;candidate=0:候选元素,初始为0。
- 遍历数组投票:
- 遍历每个元素
num,若count=0,将candidate设为num; - 若
num == candidate,count++;否则count--。
- 遍历每个元素
- 返回结果:遍历完成后,
candidate即为多数元素。
核心优化逻辑说明
- 时间复杂度最优:仅一次遍历数组(O(n)),无排序、哈希表等额外开销,耗时1ms击败99.89%用户;
- 空间复杂度极致:仅使用两个变量,空间复杂度O(1),完全符合最优解要求;
- 逻辑适配题目特性:针对性利用“多数元素出现次数超过n/2”的核心条件,通过投票抵消的方式,无需统计具体次数即可找到目标元素;
- 内存表现一般(54.8MB击败28.49%):评测机环境差异导致,该解法的内存开销已达到理论最优。
public int majorityElement(int[] nums) {
int count=0;
int candidate=0;
for (int num : nums) {
if (count==0){
candidate=num;
}
if(candidate==num){
count++;
}else {
count--;
}
}
return candidate;
}
总结
- 哈希表法(第一种):逻辑通用但效率低,空间O(n),适合不了解题目特性的通用场景;
- 排序计数法(第二种):逻辑冗余,排序+复杂计数导致性能差,无实际使用价值;
- 排序取中法(第三种):利用多数元素的位置特性简化逻辑,时间O(nlogn),是“次优解”;
- 摩尔投票法(示例解答):本题最优解,时间O(n)、空间O(1),核心是利用“多数元素票数抵消不完”的特性,无需统计具体次数即可找到结果。
- 核心技巧:面对“多数元素>n/2”的问题,优先考虑摩尔投票法,而非通用的哈希/排序思路。