【每日算法Day 90】5种方法:求解数组中出现次数超过一半的那个数

简介: 【每日算法Day 90】5种方法:求解数组中出现次数超过一半的那个数

论文中了,非常感谢各位的支持!

题目链接

LeetCode 面试题39. 数组中出现次数超过一半的数字[1]

题目描述

数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。

你可以假设数组是非空的,并且给定的数组总是存在多数元素。

说明:

  • 1 <= 数组长度 <= 50000

示例1

输入:
[1, 2, 3, 2, 2, 2, 5, 4, 2]
输出:
2

题解

哈希表

这个方法最简单,用哈希表记录每个数字出现的次数,最后看哪个数字次数超过一半就行了。

时间复杂度  ,空间复杂度  。

排序

对数组从小到大进行排序,那么众数一定在 nums[n/2] 处。为什么呢?

因为排序后相同的数都连续了,所以众数最左端的极限情况就是从下标 0 开始往后排,那么因为超过了一半,所以尾部下标一定会超过 n/2 。而最右端的极限情况就是从下标 n-1 往前排,因为超过了一半,所以头部下标也会在 n/2 之前。

综上,众数所在的区间一定会包含下标 n/2

时间复杂度  ,空间复杂度  。

随机采样

其实我第一个想到的方法反而是这个反常规的随机采样方法。因为众数超过了一半,所以采样大概率会采到这个众数。

那么我们随机采样一个数,然后遍历一遍数组看它的个数。如果个数超过了一半就是它了,否则继续采样,直到采到众数。

平均时间复杂度  ,空间复杂度  。

分治

如果把区间 [0, n-1] 平均分成两半,那么我们可以证明,原来的众数在某一半区间里依然是众数。

为什么呢?反证法,假设两半区间的众数都不是原来的众数,那么在左半区间原来的众数一定小于一半,右半区间也是的。加起来之后总数一定小于一半的,和条件是矛盾的。

所以我们递归求解两半区间的众数,然后看哪个数出现次数较多,众数就是它了。

时间复杂度  ,空间复杂度  。

摩尔投票

这个方法我一开始也想到了,但是没有想到这竟然有理论解释,而且是大名鼎鼎的摩尔投票算法。

它的主要步骤是这样的:

  • 初始化两个变量, cand 表示候选人,cnt 表示赞同它的票数。
  • 如果 cnt = 0,那么 cand 就设置为当前的数字。
  • 如果 cand 等于当前数字,那么票数 cnt 加一,否则票数减一。
  • 最后 cand 就是得票超过一半的众数。

严格证明比较复杂,是一篇论文,这里说个比较好理解的思路:

  • 如果当前候选人是众数,那么其他的众数会支持自己,其他的数反对自己。但是因为众数超过了一半,所以众数最后一定会当选。
  • 如果当前候选人不是众数,那么就惨了,其他的数和众数全都会反对他。那反对票远远超过一半了,肯定会下台,然后换候选人。
  • 上面两种情况会在 cnt = 0 的时刻进行转换,也就是换候选人。

时间复杂度  ,空间复杂度  。

代码

哈希表(c++)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        unordered_map<int, int> mp;
        for (auto x : nums) mp[x]++;
        for (auto [k, v] : mp) {
            if (v > n/2) return k;
        }
        return -1;
    }
};

排序(c++)

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

随机采样(c++)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        while (1) {
            int idx = rand() % n;
            int cnt = 0;
            for (auto x : nums) {
                if (x == nums[idx]) cnt++;
            }
            if (cnt > n/2) return nums[idx];
        }
        return -1;
    }
};

分治(c++)

class Solution {
public:
    int numCount(vector<int>& nums, int x, int l, int r) {
        int cnt = 0;
        for (int i = l; i <= r; ++i) {
            if (nums[i] == x) cnt++;
        }
        return cnt;
    }
    int findMajority(vector<int>& nums, int l, int r) {
        if (l == r) return nums[l];
        int m = l+(r-l)/2;
        int ml = findMajority(nums, l, m);
        int mr = findMajority(nums, m+1, r);
        if (ml == mr) return ml;
        int cl = numCount(nums, ml, l, r);
        int cr = numCount(nums, mr, l, r);
        return cl < cr ? mr : ml;
    }
    int majorityElement(vector<int>& nums) {
        int n = nums.size();
        return findMajority(nums, 0, n-1);
    }
};

摩尔投票(c++)

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int cand = 0, cnt = 0;
        for (auto x : nums) {
            if (!cnt) cand = x;
            if (x == cand) cnt++;
            else cnt--;
        }
        return cand;
    }
};

哈希表(python)

class Solution:
    def majorityElement(self, nums):
        counts = collections.Counter(nums)
        return max(counts.keys(), key=counts.get)

排序(python)

class Solution:
    def majorityElement(self, nums):
        nums.sort()
        return nums[len(nums)//2]

随机采样(python)

import random
class Solution:
    def majorityElement(self, nums):
        majority_count = len(nums)//2
        while True:
            candidate = random.choice(nums)
            if sum(1 for elem in nums if elem == candidate) > majority_count:
                return candidate

分治(python)

class Solution:
    def majorityElement(self, nums, lo=0, hi=None):
        def majority_element_rec(lo, hi):
            if lo == hi:
                return nums[lo]
            mid = (hi-lo)//2+lo
            left = majority_element_rec(lo, mid)
            right = majority_element_rec(mid+1, hi)
            if left == right:
                return left
            left_count = sum(1 for i in range(lo, hi+1) if nums[i] == left)
            right_count = sum(1 for i in range(lo, hi+1) if nums[i] == right)
            return left if left_count > right_count else right
        return majority_element_rec(0, len(nums)-1)

摩尔投票(python)

class Solution:
    def majorityElement(self, nums):
        count = 0
        candidate = None
        for num in nums:
            if count == 0:
                candidate = num
            count += (1 if num == candidate else -1)
        return candidate
相关文章
|
5天前
|
存储 算法 Go
算法学习:数组 vs 链表
算法学习:数组 vs 链表
12 0
|
8天前
|
机器学习/深度学习 算法 C语言
详细介绍递归算法在 C 语言中的应用,包括递归的基本概念、特点、实现方法以及实际应用案例
【6月更文挑战第15天】递归算法在C语言中是强大力量的体现,通过函数调用自身解决复杂问题。递归涉及基本概念如自调用、终止条件及栈空间管理。在C中实现递归需定义递归函数,分解问题并设定停止条件。阶乘和斐波那契数列是经典应用示例,展示了递归的优雅与效率。然而,递归可能导致栈溢出,需注意优化。学习递归深化了对“分而治之”策略的理解。**
22 7
|
13天前
|
存储 SQL 算法
LeetCode第53题:最大子数组和【python 5种算法】
LeetCode第53题:最大子数组和【python 5种算法】
|
9天前
|
算法
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
【经典LeetCode算法题目专栏分类】【第10期】排序问题、股票问题与TOP K问题:翻转对、买卖股票最佳时机、数组中第K个最大/最小元素
|
16天前
|
存储 算法
数据结构和算法学习记录——设计循环队列(数组实现循环队列)核心思路、题解过程、完整题解
数据结构和算法学习记录——设计循环队列(数组实现循环队列)核心思路、题解过程、完整题解
17 1
|
17天前
|
存储 算法
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
算法训练,牛客.判断是不是平衡二叉树 牛客.最大子矩阵两个数组的交集牛客.数组中两个字符串的最小距离
|
2天前
|
算法
【洛谷 P1003】[NOIP2011 提高组] 铺地毯 题解(数组+贪心算法)
**NOIP2011 提高组问题:铺地毯** 在第一象限的颁奖典礼场地,有$n$张地毯按编号顺序铺设。需找出覆盖特定点$(x, y)$的最上方地毯编号。输入包括地毯坐标和点坐标,输出地毯编号或-1表示未覆盖。 样例:给定3张地毯,点$(2,2)$被第3张地毯覆盖,输出3;另一样例点$(4,5)$未被覆盖,输出-1。 $30\%$数据$n\leq2$,$50\%$数据坐标$\leq100$,$100\%$数据$n\leq10^4$,坐标$\leq10^5$。 解决方法:从下到上遍历地毯,更新覆盖点的地毯编号。 AC代码略。
5 0
|
9天前
|
算法
【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值
【经典LeetCode算法题目专栏分类】【第6期】二分查找系列:x的平方根、有效完全平方数、搜索二位矩阵、寻找旋转排序数组最小值
|
13天前
|
算法 数据可视化 数据挖掘
最佳加油站选择算法:解决环路加油问题的两种高效方法|LeetCode力扣134
最佳加油站选择算法:解决环路加油问题的两种高效方法|LeetCode力扣134
|
13天前
|
算法 数据挖掘 定位技术
算法必备数学基础:图论方法由浅入深实践与应用
算法必备数学基础:图论方法由浅入深实践与应用