剑指offer 39. 数组中出现次数超过一半的数字(哈希;排序;摩尔投票)

2020年12月18日 周五 天气晴 【不悲叹过去,不荒废现在,不惧怕未来】



题目简介

剑指 Offer 39. 数组中出现次数超过一半的数字
在这里插入图片描述

1. 哈希表

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int,int> record; // 元素->频率
        for(int i=0;i<nums.size();++i)
        {
        	// 边统计个数,边判断是否为众数
            record[nums[i]]++;
            if(record[nums[i]]>nums.size()/2)return nums[i];
        }
        return -1;
    }
};
  • 时间复杂度: O ( n ) O\left( {n} \right) O(n)
  • 空间复杂度: O ( n ) O\left( {n} \right) O(n)

2. 排序

由于众数出现的频率大于n/2,所以在排序之后众数必存在于下标[n/2]处(本题默认数组中是一定存在众数的),所以返回下标为[n/2]的元素即可(其实就是返回数组中第[n/2]大的数)。

2.1 STL库函数sort()

直接使用STL中的排序函数sort()

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size()/2];
    }
};
  • 时间复杂度: O ( n l o g n ) O\left( {nlogn} \right) O(nlogn)
  • 空间复杂度: O ( l o g n ) O\left( {logn} \right) O(logn)

2.2 快速选择算法(quick select)

寻找数组中第[n/2]大的数就是TopK问题的变形,我们在 LeetCode 215. 数组中的第K个最大元素(快排、堆排的应用) 详细讨论过,这里使用快速选择算法来解决这个问题。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int len = nums.size();
        // 设置随机数种子
        srand(time(0));
        // 返回升序排列中第 len/2 大的元素
        return quickSelect(nums,0,len-1,(len-1)/2);
    }

    // 基于快排的选择方法(quick select)
    int quickSelect(vector<int>& nums, int l, int r, int k){
        int rand_idx = rand() % (r-l+1) + l;
        swap(nums[l],nums[rand_idx]);

        int idx = partition(nums,l,r);
        if(idx==k) return nums[idx];
        else return idx<k?quickSelect(nums,idx+1,r,k):quickSelect(nums,l,idx-1,k);
    }

    // 分割操作(以中轴元素为基准,将数组分割成三部分,返回中轴元素索引)
    int partition(vector<int>& nums, int l, int r){
        int pivot = nums[l]; // 取第一个位置的元素作为中轴元素

        // 右边主动接近左边,进行循环覆盖
        while(l < r){
            // 从右边寻找第一个大于 pivot 的数
            while(l < r && nums[r] > pivot) --r; 
            // 找到后直接进行覆盖
            nums[l] = nums[r];
            // 从左边寻找第一个小于等于 pivot 的数
            while(l < r && nums[l] <= pivot) ++l; 
            // 找到后直接进行覆盖
            nums[r] = nums[l];
        }
        
        // 把pivot交换到中间,完成分割
        nums[l] = pivot;
        return l;
    }
};
  • 时间复杂度:由于引入了随机化,时间复杂度的期望值是 O ( n ) O\left( {n} \right) O(n)
  • 空间复杂度:递归调用的期望深度为 O ( l o g n ) O\left( {logn} \right) O(logn)

注意: 虽然快速选择算法时间复杂度的期望值为 O ( n ) O\left( {n} \right) O(n),但是LeetCode上有很多极端示例,导致算法超时了,无法通过。

3. 摩尔投票

摩尔投票法:遇到相同的数,就投一票,遇到不同的数,就减一票(减到小于0就更换候选数字),最后还存在票的数就是众数。

  • 摩尔投票算法是基于这个事实:每次从序列里选择两个不相同的数字删除掉(或称为“抵消”),最后剩下一个数字或几个相同的数字,就是出现次数大于总数一半的那个
class Solution {
public:
	// 摩尔投票法:遇到相同的数,就投一票,遇到不同的数,就减一票,最后还存在票的数就是众数
    int majorityElement(vector<int>& nums) {
        int cnt = 0;
        int res = 0;
        for(int i=0;i<nums.size();++i){
            if(res==nums[i]) ++cnt;
            else if(--cnt<0){
                res = nums[i];
                cnt = 1;
            }
        }
        return res;
    }
};
  • 时间复杂度: O ( n ) O\left( {n} \right) O(n)
  • 空间复杂度: O ( 1 ) O\left( {1} \right) O(1)

参考文献

《剑指offer 第二版》

https://leetcode-cn.com/problems/majority-element/solution/duo-shu-yuan-su-by-leetcode-solution/

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值