力扣解题-169. 多数元素

6 阅读7分钟

力扣解题-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”的条件,逻辑直观但时间/空间效率一般。

具体步骤:

  1. 边界处理:若数组长度<2,直接返回唯一元素(必然是多数元素);
  2. 初始化变量
    • halfLength = nums.length/2:多数元素的频次阈值;
    • map:HashMap<Integer, Integer>,键为数组元素,值为对应出现次数;
    • much:存储最终找到的多数元素,初始为0。
  3. 遍历数组统计频次
    • 遍历每个元素i,若map中已包含该元素,将计数+1;若计数超过halfLength,立即记录该元素为much
    • map中未包含该元素,初始化计数为1。
  4. 返回结果:遍历完成后返回much

性能劣势说明

该解法耗时18ms仅击败5.37%用户,核心问题是:

  1. 时间复杂度的额外开销:哈希表的containsKeygetput操作存在哈希冲突的可能(最坏O(n)),且遍历过程中需频繁更新/查询哈希表,总时间复杂度为O(n)但常数因子较大;
  2. 空间复杂度非最优:哈希表需存储所有不同元素,空间复杂度O(n),违背“最优解O(1)空间”的要求;
  3. 内存表现较好(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;
    }

第二种解答

解题思路

核心方法:排序 + 遍历计数,先对数组排序(重复元素连续),再遍历统计元素出现次数,对比当前最多计数,逻辑冗余且性能较差。

具体步骤:

  1. 边界处理:数组长度<2时直接返回唯一元素;
  2. 排序预处理:调用Arrays.sort(nums),使重复元素连续排列;
  3. 初始化计数变量currentElement(当前统计元素)、currentNum(当前计数)、nextElement(新元素)、nextCountNum(新元素计数);
  4. 遍历统计次数
    • 第一个元素初始化currentElementcurrentNum
    • 后续元素若与currentElement相同则计数+1,若超过n/2则终止遍历;
    • 若为新元素则初始化nextCountNum,并对比是否超过当前最大计数,更新currentElement
  5. 返回结果:返回最终的currentElement

性能劣势说明

该解法耗时6ms击败27.13%用户,核心问题是:

  1. 排序的额外开销:排序时间复杂度为O(nlogn),远高于最优解的O(n);
  2. 计数逻辑冗余:排序后重复元素已连续,无需维护nextElementnextCountNum,只需单变量计数即可,多余的变量和判断增加了时间常数;
  3. 内存表现一般(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)必然是该元素。

具体步骤:

  1. 边界处理:数组长度<2时直接返回唯一元素;
  2. 排序数组Arrays.sort(nums)使元素有序;
  3. 取中间值:计算索引index = nums.length/2,返回nums[index]

性能说明

该解法耗时5ms击败39.34%用户,相比前两种有优化但仍有短板:

  1. 时间复杂度仍为O(nlogn):排序的开销是核心瓶颈,虽简化了后续逻辑,但无法达到最优的O(n);
  2. 空间复杂度:排序的栈空间开销(O(logn)),高于最优解的O(1);
  3. 内存表现差(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--(投票抵消);
  • 最终剩下的候选元素必然是多数元素(题目保证存在多数元素)。

具体步骤:

  1. 初始化变量
    • count=0:投票计数,初始为0;
    • candidate=0:候选元素,初始为0。
  2. 遍历数组投票
    • 遍历每个元素num,若count=0,将candidate设为num
    • num == candidatecount++;否则count--
  3. 返回结果:遍历完成后,candidate即为多数元素。
核心优化逻辑说明
  1. 时间复杂度最优:仅一次遍历数组(O(n)),无排序、哈希表等额外开销,耗时1ms击败99.89%用户;
  2. 空间复杂度极致:仅使用两个变量,空间复杂度O(1),完全符合最优解要求;
  3. 逻辑适配题目特性:针对性利用“多数元素出现次数超过n/2”的核心条件,通过投票抵消的方式,无需统计具体次数即可找到目标元素;
  4. 内存表现一般(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;
    }

总结

  1. 哈希表法(第一种):逻辑通用但效率低,空间O(n),适合不了解题目特性的通用场景;
  2. 排序计数法(第二种):逻辑冗余,排序+复杂计数导致性能差,无实际使用价值;
  3. 排序取中法(第三种):利用多数元素的位置特性简化逻辑,时间O(nlogn),是“次优解”;
  4. 摩尔投票法(示例解答):本题最优解,时间O(n)、空间O(1),核心是利用“多数元素票数抵消不完”的特性,无需统计具体次数即可找到结果。
  5. 核心技巧:面对“多数元素>n/2”的问题,优先考虑摩尔投票法,而非通用的哈希/排序思路。