【Warrior刷题笔记】力扣169. 多数元素 【排序 || 哈希 || 随机算法 || 摩尔投票法】详细注释 不断优化 极致压榨
题目
来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/majority-element/ 注意,该题在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 ) ++count;//统计以随机数为下标的元素出现次数 } return nums ;//若以该随机数为下标的元素出现次数超过数组元素数的一半,返回以该随机数为下标的元素 } };
复杂度分析
时间复杂度: O(m)。理论上最坏情况下复杂度为O(∞),但实际上由于多数元素出现次数超过数组元素数的一半,平均情况下只需要两次就能选出多数元素,主要的时间开销是统计出现次数,其时间复杂度为O(m)。 空间复杂度: O(1)。只需常数个额外空间。
解法四 摩尔投票法
解题思路
面试官觉得你这解法很有意思,然后又问,那要我就是非酋呢,那岂不是无限循环了,有没有更好的办法。 这下可让人犯了难,有什么更好的办法既能时间最优又能空间最优呢? 考虑这样一个场景,一群人打擂台,人群中分为不同的帮派,挨个上台比武。留在台上的帮派为擂主帮派,如果上台的人不是自己帮派的人,则将其干掉,代价是损失一个帮派成员;如果上台的人是自己帮派的人,则将其留下。则当所有人都参与完比武后,留在台上的必然可以是人数超过比武人数一半的帮派。而在本题中,多数元素出现次数超过数组元素数的一半,也即,其他所有元素加起来都没多数元素多。如果我们记多数元素的值为1,其他元素的值为0,将数组中所有元素值相加,结果必然大于0。于是我们可以设计这样一种算法:
算法
1.初始化候选答案ans为nums[0],初始化数值计算结果count为0; 2.遍历数组若该元素与备选答案相等,count加一,否则减一; 3.若count减为零,更换备选答案ans为当前元素,重置count为1,继续遍历后续元素。 遍历完成后返回最终的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
可可爱爱的蕾姆镇
- 力扣169题:多数元素,Boyer-Moore投票算法学习笔记,python实现
- 力扣解题思路:169. 多数元素
- 多数元素(力扣169题)
- 【LeetCode】169. 多数元素/众数(JAVA)摩尔投票
- python【力扣LeetCode算法题库】169 多数元素
- leetcode 169. 多数元素
- 排序-快速排序-优化-使用三向切分(优化重复元素的情况
- 004-寻找多数元素-归纳法-《算法设计技巧与分析》M.H.A学习笔记
- 基本排序(二):选择排序 - 不断选出剩余数组中最小的元素
- Nginx 1.10.1 版本nginx.conf优化配置及详细注释
- 【数据结构与算法】内部排序之一:插入排序和希尔排序的N中实现(不断优化,附完整源码)
- Java数组(算法169,747):多数元素,至少是其他数字两倍的最大数
- LeetCode-169:Majority Element (数组中的多数元素)
- Nginx 1.10.1 版本nginx.conf优化配置及详细注释
- LeetCode刷题笔记82: 删除排序链表中的重复元素 II(Python实现)
- 《Algorithms算法》笔记:元素排序(1)——简单排序
- 王道c语言-c++-排序-选择排序/插入排序/快速排序(快排)-学习笔记(代码+注释)
- jQuery 笔记,详细且基础,源码,注释,案例 三合一
- leetcode练习之数组169. 多数元素
- 传智博客佟老师jqurey学习笔记,以及例子代码详细注释。