题目

来源:力扣、牛客
链接1:https://leetcode-cn.com/problems/majority-element/
链接2:牛客地址
注意,该题在LC中被标注为easy,所以我们更多应该关注的是文章中不断优化的思路和方法。很多时候面试考察的,就是与面试官一起做题并把时间复杂度和空间复杂度压榨到极致的过程中你所表现出来的能力。

1.描述

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

2.示例
  • 示例 1:
输入:[3,2,3]
输出:3
  • 示例 2:
输入:[2,2,1,1,1,2,2]
输出:2

解法一 排序

解题思路

很快啊,啪的一下我们想到,由于多数元素是指在数组中出现次数 大于 ⌊n/2⌋ 的元素,那么将数组排序后,即便该元素是最小元素或是最大元素,其在数组中的覆盖范围也超过了数组中点(从左到右超过或是从右到左超过)。于是得到算法:

算法

1.将数组排序;
2.返回数组中点元素nums[nums.size()/2].

代码

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());//对数组进行排序
        return nums[nums.size()/2];//返回数组中点元素
    }
};

复杂度分析

时间复杂度: O(mlogm)m为数组元素数,主要是排序的时间消耗。
空间复杂度: O(logm)。主要是系统sort函数需要的栈空间消耗。

解法二 哈希

解题思路

这时候,面试官说,你这解法一的时间复杂度很大啊,你能优化一下吗。
稍加思索,我们想到,如果考虑使用哈希表空间换时间,则可以降低时间复杂度。我们可以使用哈希表存储数组中元素出现的次数,当某个元素的出现次数超过一半时,返回该元素。于是得到算法:

算法

1.初始化存储数组元素及其出现次数的哈希表map1,键为元素值,值为该元素在数组中的出现次数;
2.遍历数组元素,将被遍历数组元素的哈希值加一;
3.若该元素在数组中的出现次数超过数组元素数的一半,返回该元素。

代码

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> map1;//存储数组元素值及出现次数的哈希表
        for(int i = 0; i < nums.size(); i++)//遍历数组元素
        {
            map1[nums[i]]++;//该元素出现次数加一
            if(map1[nums[i]] > nums.size()/2)//若该元素出现此处超过一半
            {
                return nums[i];//返回该元素
            }
        }
        return 0;//为了能通过编译
    }
};

复杂度分析

时间复杂度: O(m)。遍历数组元素的时间开销。
空间复杂度: O(m)。哈希表的空间消耗。

解法三 随机算法

解题思路

这时候面试官又说了,你这解法二空间复杂度连解法一都不如,再想想有没有更好的解法。
手扶下巴,眉头一皱,我们又想到,由于多数元素是指在数组中出现次数大于⌊n/2⌋ 的元素,因此如果我们随机选取数组中的一个元素,那么将有超过1/2的概率选到该多数元素。于是可以使用这种算法:

算法

1.在数组中随机挑选一个元素; 2.遍历数组统计该元素出现次数,若超过数组元素数的一半,返回该元素,否则重新随机挑选一个元素。

代码

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int m = nums.size();//计算数组元素数
        srand(time(NULL));//设置随机数种子
        int n;//存储随机数
        int count = 0;//初始化以随机数为下标的元素的出现次数为0
        while(count<=m/2){//当以随机数为下标的元素出现次数未超过数组元素数的一般
            n = rand()%m;//获取一个随机数
            count = 0;//初始化以随机数为下标的元素出现次数为0
            for(int num : nums) if(num == nums[n]) ++count;//统计以随机数为下标的元素出现次数
        }
        return nums[n];//若以该随机数为下标的元素出现次数超过数组元素数的一半,返回以该随机数为下标的元素
    }
};

复杂度分析

时间复杂度: O(m)。理论上最坏情况下复杂度为O(∞),但实际上由于多数元素出现次数超过数组元素数的一半,平均情况下只需要两次就能选出多数元素,主要的时间开销是统计出现次数,其时间复杂度为O(m)
空间复杂度: O(1)。只需常数个额外空间。

解法四 摩尔投票法

解题思路

面试官觉得你这解法很有意思,然后又问,那要我就是非酋呢,那岂不是无限循环了,有没有更好的办法。
这下可让人犯了难,有什么更好的办法既能时间最优又能空间最优呢?
考虑这样一个场景,一群人打擂台,人群中分为不同的帮派,挨个上台比武。留在台上的帮派为擂主帮派,如果上台的人不是自己帮派的人,则将其干掉,代价是损失一个帮派成员;如果上台的人是自己帮派的人,则将其留下。则当所有人都参与完比武后,留在台上的必然是人数最多的帮派。
而在本题中,多数元素出现次数超过数组元素数的一半,也即,其他所有元素加起来都没多数元素多。如果我们记多数元素的值为1,其他元素的值为0,将数组中所有元素值相加,结果必然大于0。于是我们可以设计这样一种算法:

算法

1.初始化候选答案ansnums[0],初始化数值计算结果count0
2.遍历数组若该元素与备选答案相等,count加一,否则减一;
3.若count减为零,更换备选答案ans为当前元素,重置count1,继续遍历后续元素。
遍历完成后返回最终的ans即为多数元素。

代码

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int ans = nums[0];//初始化候选答案为nums[0]
        int count = 0;//初始化数值计算结果为0
        for(auto num : nums){//遍历数组
            if(num==ans) ++count;//若该元素与备选答案相同,则count加一
            else{//否则减一
                --count;
                if(count==0){//若count减为0
                    ans = num;//更换备选答案为当前元素
                    ++count;//重置count为1
                }
            }
        }
        return ans;//返回答案
    }
};

复杂度分析

时间复杂度: O(m)。只需要对数组进行一次遍历即可。
空间复杂度: O(1)。只需常数个额外空间。

Tips:

由于题目明确说明给定的数组中总是存在多数元素,因此本题不用考虑数组不存在多数元素的情况。若未做说明,需要像解法三那样,在选出ans以后再进行一次统计,验证该元素是否是多数元素,若果是,返回之;如果不是,返回不存在多数元素。时间复杂度和空间空间复杂度不变。

更多知识内容分享:
力扣个人主页https://leetcode-cn.com/profile/articles/
CSDN个人主页https://blog.csdn.net/qq_39255924
牛客个人主页https://blog.nowcoder.net/newcoderthewarrior

可可爱爱的蕾姆镇
alt