LeetCode面试150——169多数元素

题目难度:简单

默认优化目标:最小化平均时间复杂度。

Python默认为Python3。

目录

1 题目描述

2 题目解析

3 算法原理及程序实现

3.1 暴力求解(不推荐)

3.2 排序

3.3 哈希表

3.4 分治

3.5 Boyer-Moore投票算法

3.6 随机法

附录 分治法的时间复杂度求取

参考文献


1 题目描述

给定一个大小为 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

进阶:尝试设计时间复杂度为 O(n)、空间复杂度为 O(1) 的算法解决此问题。

2 题目解析

这个题目描述十分简洁,没有什么弯弯绕的地方。

输入是长度为n的数组nums,输出是出现次数大于[n/2]的元素。

输出只可能是一个元素,因为出现次数大于[n/2],假设存在2个及以上的元素满足条件,数组的超度就超过n了,与已知条件不符。

最先想到的是暴力求解,即用一个计数器去统计元素出现的次数。那这样平均时间复杂度为O(n^2)。对于面试,这样写出来等于没写,因为人人都会这种方法且这种方法效果太差。那我们就需要用别的方法来降低时间复杂度。

3 算法原理及程序实现

3.1 暴力求解(不推荐)

平均时间复杂度O(n^2),平均空间复杂度O(1)。会超出力扣的运行时间限制。

C++代码实现

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
​
        for (int i = 0; i < n; i++) {
            int count = 0;//判断每个元素出现的次数前,计数器先清零
            for (int j = i; j < n; j++) {//相同的元素在前面位置的,前面能满足条件则满足,满足不了后面也满足不了
                if (nums[j] == nums[i]) {
                    count++;
                }
            }
            if (count > n / 2) {
                return nums[i];//满足条件输出,程序结束
            }
        }
        return -1; 
    }
    
};

Python代码实现

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        n=len(nums)
​
        for i in range(0,n):
            count=0
            for j in range(i,n):
                if nums[i]==nums[j]:
                    count+=1
            if count>n/2:
                return nums[i]
​
        return -1

3.2 排序

我们注意到,nums中出现次数大于[n/2] 的元素,如果经过排序后,则该元素必然在nums[n/2]

如果使用快排,则平均时间复杂度为O(n\log n),平均空间复杂度为O(\log n)

C++代码实现

class Solution {
public:
    int majorityElement(vector<int>& nums) {
​
        sort(nums.begin(), nums.end());
        return nums[nums.size()/2];
    }
};

Python代码实现

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        nums.sort()
        return nums[len(nums) // 2]
​

3.3 哈希表

我们可以使用哈希表来快速统计每个元素出现的次数,通过返回值最大的所对应的键,即可找到出现次数大于n/2的元素。

平均时间复杂度O(n),平均空间复杂度O(n)

C++程序实现

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int>counts;//创建一个哈希表<key,value>,键值都是int型
        int majority=0,cnt=0;//majority用于记录出现次数大于n/2的元素,cnt用于记录次数
​
        for(int num: nums){//引用方式,通过键遍历哈希表
            ++counts[num];
            if(counts[num]>cnt){//如果元素的出现次数大于当前最大的出现次数,更新键值
                majority=num;
                cnt=counts[num];
            }
        }
        return majority;
        
    }
};
Python代码实现
class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        counts=collections.Counter(nums)#计数可哈希对象的出现次数并创建一个字典
        return max(counts.keys(),key=counts.get)#获取每个键的出现次数,返回出现次数最多的那个键的值,即所求输出元素

3.4 分治

原理:如果anums的众数,则将nums分成两部分后,a必是至少一部分的众数。

假设n为偶数,如果a不是任意两部分的众数,临界情况是ab都出现n/2次且nums从中间均分,两部分ab的数量各占一半。但a出现次数大于n/2,所以原理成立。n为奇数同理,因为a的出现次数大于n/2,原理成立。

为了使用递归,我们每次将数组从中间分成左右两部分,然后左数组和右数组再分成左右两部分,直到数组的长度为1,这样这个数必然是众数。然后回溯区间,判断这个众数是不是这个区间的众数。是则是,否则我们需要比较这两个众数在整个区间内出现的次数来决定该区间的众数。

平均时间复杂度为O(n \log n),平均空间复杂度为O(\log n)​。

对这部分的时间复杂度求取见附录。

C++代码实现。

class Solution {
    int count_in_range(vector<int>& nums, int target, int lo, int hi) {//计算一段区间内的众数
        int count = 0;
        for (int i = lo; i <= hi; ++i)
            if (nums[i] == target)
                ++count;
        return count;
    }
    int majority_element_rec(vector<int>& nums, int lo, int hi) {
        if (lo == hi)//如果长度为1,直接返回
            return nums[lo];
        int mid = (lo + hi) / 2;//分治
        int left_majority = majority_element_rec(nums, lo, mid);//[lo,mid],递归
        int right_majority = majority_element_rec(nums, mid + 1, hi);//[mid+1,hi],递归
        if (count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) / 2)//左边众数出现次数多于右边,返回左边
            return left_majority;
        if (count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) / 2)//右边众数出现次数多于左边,返回右边
            return right_majority;
        return -1;
    }
public:
    int majorityElement(vector<int>& nums) {
        return majority_element_rec(nums, 0, nums.size() - 1);
    }
};
​

Python代码实现

class Solution:
    def count_in_range(self, nums, target, lo, hi)->int:
        count = 0
        for i in range(lo, hi + 1):
            if nums[i] == target:
                count += 1
        return count
​
    def majority_element_rec(self, nums, lo, hi)->int:
        if lo == hi:  # 如果长度为1,直接返回
            return nums[lo]
        
        mid = (lo + hi) // 2  # 分治
        left_majority = self.majority_element_rec(nums, lo, mid)  # [lo, mid],递归
        right_majority = self.majority_element_rec(nums, mid + 1, hi)  # [mid+1, hi],递归
        
        if self.count_in_range(nums, left_majority, lo, hi) > (hi - lo + 1) // 2:  # 左边众数出现次数多于右边,返回左边
            return left_majority
        if self.count_in_range(nums, right_majority, lo, hi) > (hi - lo + 1) // 2:  # 右边众数出现次数多于左边,返回右边
            return right_majority
        
        return -1
​
    def majorityElement(self, nums: List[int])->int:
        return self.majority_element_rec(nums, 0, len(nums) - 1)

3.5 Boyer-Moore投票算法

上帝视角:我们将nums中的元素依次和真正的众数mode去比,是+1,否-1,用val来记录。

平民视角:设置一个计数器。首先我们将nums的第一个元素设置成众数,依次和后面的数去比较。和此众数相同则+1,不同则-1。当count=0时,记下一个元素为众数。重复此步骤。

示例如下:


nums:[7,7,5,7,5,1|5,7|5,5,7,7|7,7,7,7]\\ count:[1,2,1,2,1,0|1,0|1,2,1,0|1,2,3,4]\\ val: [1,2,1,2,1,0|-1,0|-1,-2,-1,0|1,2,3,4]
 

我们可以发现,count要么和val相等,要么相反。因此我们可以用过count来找到众数。count为负时我们将其取正即可,即当count=-1,令count=1

平均时间复杂度为O(n),平均空间复杂度为O(1)

C++代码实现

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int mode = -1;
        int count = 0;
        for (int num : nums) {
            if (num == mode)
                ++count;
            else if (--count < 0) {//否则count自减1并且进行是否为0判断
                mode = num;
                count = 1;//相当于变成val
            }
        }
        return mode;
    }
};
​

Python代码实现

    def majorityElement(self, nums: List[int]) -> int:
        mode,count=None,0
​
        for num in nums:
            if count==0:
                mode=num
            count+=(1 if num==mode else -1)
        
        return mode

3.6 随机法

因为出现次数大于n/2,我们随机挑选一个数,这个数为众数的可能性很大。我们检验我们挑的数是否为众数,是输出,否重复上述步骤。

平均时间复杂度为O(n),平均空间复杂度为O(1)

C++实现代码

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        while (true) {
            int mode = nums[rand() % nums.size()];
            int count = 0;
            for (int num : nums)
                if (num == mode)
                    ++count;
            if (count > nums.size() / 2)
                return mode;
        }
        return -1;
    }
};

Pyhon实现代码

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        majority_count = len(nums) // 2
        while True:
            mode = random.choice(nums)
            if sum(1 for elem in nums if elem == mode) > majority_count:
                return mode

附录 分治法的时间复杂度求取

主定理用于解决分治法递归关系式,适用形式

T(n)=aT(b/2)+f(n)

用下面的法则来确定复杂度

情况1:
if (f(n) = O(n^c)) and (c < \log_b{a}), then (T(n) = \Theta(n^{\log_b{a}}))

情况2:

if (f(n) = \Theta(n^{\log_b{a}})),then (T(n) = \Theta(n^{\log_b{a}} \log{n}))\\

情况3:

if (f(n) = \Omega (n^c)) and (c > log_b{a}), and if (a f(n/b) \leq k f(n)) for some k < 1 and sufficiently large n, then (T(n) = \Theta (f(n)))
 

本文中的分治法为分解成2个长度为n/2的子问题,做了两遍长度为n的线性扫描(第一遍为n/2+n/2=n,第二遍为n/4+n/4=n/2,第三遍为n/4,...  ,第k遍为n/2^k,用等比数列求和公式得到2n),故
T(n)=2T(n/2)+2n

因此
a=2,\ b=2, \ f(n)=2n

所以适用情况2,故时间复杂度为
T(n) = \Theta(n^{\log_b{a}} \log{n})=\Theta(n^{\log_22}\log n)=\Theta(n\log n)
 

参考文献

力扣面试经典150题

力扣官方题解

  • 17
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值