排序题目:多数元素 II

文章目录

题目

标题和出处

标题:多数元素 II

出处:229. 多数元素 II

难度

3 级

题目描述

要求

给定大小为 n \texttt{n} n 的数组 nums \texttt{nums} nums,找出其中所有出现超过 ⌊ n 3 ⌋ \Big\lfloor \dfrac{\texttt{n}}{\texttt{3}} \Big\rfloor ⌊3n⌋ 次的元素。

示例

示例 1:

输入: nums = [3,2,3] \texttt{nums = [3,2,3]} nums = [3,2,3]

输出: [3] \texttt{[3]} [3]

示例 2:

输入: nums = [1] \texttt{nums = [1]} nums = [1]

输出: [1] \texttt{[1]} [1]

示例 3:

输入: nums = [1,2] \texttt{nums = [1,2]} nums = [1,2]

输出: [1,2] \texttt{[1,2]} [1,2]

数据范围

  • n = nums.length \texttt{n} = \texttt{nums.length} n=nums.length
  • 1 ≤ n ≤ 5 × 10 4 \texttt{1} \le \texttt{n} \le \texttt{5} \times \texttt{10}^\texttt{4} 1≤n≤5×104
  • -10 9 ≤ nums[i] ≤ 10 9 \texttt{-10}^\texttt{9} \le \texttt{nums[i]} \le \texttt{10}^\texttt{9} -109≤nums[i]≤109

进阶

你可以使用线性时间复杂度和 O(1) \texttt{O(1)} O(1) 空间复杂度解决此问题吗?

前言

这道题是「多数元素」的进阶,要求找出数组中所有出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素。这道题也可以使用哈希表计数、排序和摩尔投票三种解法得到答案。

长度是 n n n 的数组中,最多有 2 2 2 个出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素。可以使用反证法证明。

假设有 3 3 3 个出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素,这 3 3 3 个元素的出现次数都不小于 ⌊ n 3 ⌋ + 1 \Big\lfloor \dfrac{n}{3} \Big\rfloor + 1 ⌊3n⌋+1,因此这 3 3 3 个元素的总出现次数至少为 3 × ⌊ n 3 ⌋ + 3 3 \times \Big\lfloor \dfrac{n}{3} \Big\rfloor + 3 3×⌊3n⌋+3。由于 ⌊ n 3 ⌋ > n 3 − 1 \Big\lfloor \dfrac{n}{3} \Big\rfloor > \dfrac{n}{3} - 1 ⌊3n⌋>3n−1,因此 3 × ⌊ n 3 ⌋ + 3 > 3 × ( n 3 − 1 ) + 3 = 3 × n 3 − 3 + 3 = n 3 \times \Big\lfloor \dfrac{n}{3} \Big\rfloor + 3 > 3 \times (\dfrac{n}{3} - 1) + 3 = 3 \times \dfrac{n}{3} - 3 + 3 = n 3×⌊3n⌋+3>3×(3n−1)+3=3×3n−3+3=n,即这 3 3 3 个元素的总出现次数一定超过 n n n,和数组长度是 n n n 矛盾。因此数组中不可能有 3 3 3 个出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素,最多有 2 2 2 个出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素。

解法一

思路和算法

最直观的解法是统计数组中每个元素的出现次数,然后寻找出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素。

遍历数组,使用哈希表记录每个元素的出现次数,遍历结束之后即可得到数组中每个元素的出现次数。然后遍历哈希表,对于哈希表中的每个元素得到出现次数,将出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素添加到结果中。

代码

java 复制代码
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
        for (int num : nums) {
            counts.put(num, counts.getOrDefault(num, 0) + 1);
        }
        List<Integer> majorities = new ArrayList<Integer>();
        int n = nums.length;
        Set<Integer> set = counts.keySet();
        for (int num : set) {
            if (counts.get(num) > n / 3) {
                majorities.add(num);
            }
        }
        return majorities;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。遍历数组统计每个元素的出现次数需要 O ( n ) O(n) O(n) 的时间,遍历哈希表得到多数元素也需要 O ( n ) O(n) O(n) 的时间。

  • 空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要创建哈希表记录每个元素的出现次数,哈希表中的元素个数不超过 n n n。

解法二

思路和算法

首先将数组排序,排序后的数组满足相等的元素一定出现在数组中的相邻位置。如果一个元素在数组中的出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋,则排序后的数组中存在至少 ⌊ n 3 ⌋ + 1 \Big\lfloor \dfrac{n}{3} \Big\rfloor + 1 ⌊3n⌋+1 个连续的元素都等于该元素,即一定存在两个差为 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的下标处的元素都等于该元素。

将数组 nums \textit{nums} nums 排序之后遍历数组 nums \textit{nums} nums,对于下标 i i i,当 i ≥ ⌊ n 3 ⌋ i \ge \Big\lfloor \dfrac{n}{3} \Big\rfloor i≥⌊3n⌋ 时,如果 nums [ i ] = nums [ i − ⌊ n 3 ⌋ ] \textit{nums}[i] = \textit{nums}[i - \Big\lfloor \dfrac{n}{3} \Big\rfloor] nums[i]=nums[i−⌊3n⌋],则 nums [ i ] \textit{nums}[i] nums[i] 是出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素。

为了避免重复计算,当 i < n − 1 i < n - 1 i<n−1 且 nums [ i ] = nums [ i + 1 ] \textit{nums}[i] = \textit{nums}[i + 1] nums[i]=nums[i+1] 时跳过下标 i i i,只有当下标 i i i 的右侧没有与 nums [ i ] \textit{nums}[i] nums[i] 相等的元素时才判断 nums [ i ] = nums [ i − ⌊ n 3 ⌋ ] \textit{nums}[i] = \textit{nums}[i - \Big\lfloor \dfrac{n}{3} \Big\rfloor] nums[i]=nums[i−⌊3n⌋] 是否成立,如果成立则将 nums [ i ] \textit{nums}[i] nums[i] 添加到结果中。

代码

java 复制代码
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        Arrays.sort(nums);
        List<Integer> majorities = new ArrayList<Integer>();
        int n = nums.length;
        for (int i = n / 3; i < n; i++) {
            int num = nums[i];
            if (i < n - 1 && num == nums[i + 1]) {
                continue;
            }
            if (num == nums[i - n / 3]) {
                majorities.add(num);
            }
        }
        return majorities;
    }
}

复杂度分析

  • 时间复杂度: O ( n log ⁡ n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( n log ⁡ n ) O(n \log n) O(nlogn) 的时间。

  • 空间复杂度: O ( log ⁡ n ) O(\log n) O(logn),其中 n n n 是数组 nums \textit{nums} nums 的长度。排序需要 O ( log ⁡ n ) O(\log n) O(logn) 的递归调用栈空间。

解法三

思路和算法

原始的摩尔投票算法用于找到出现次数大于一半的元素,其时间复杂度是 O ( n ) O(n) O(n),空间复杂度是 O ( 1 ) O(1) O(1)。摩尔投票算法可以推广到寻找出现次数大于 ⌊ n k ⌋ \Big\lfloor \dfrac{n}{k} \Big\rfloor ⌊kn⌋ 的元素,其中 k k k 是大于 1 1 1 的正整数。

由于出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 的元素不可能超过 2 2 2 个,因此维护 2 2 2 个候选元素 majority 1 \textit{majority}_1 majority1 和 majority 2 \textit{majority}_2 majority2,以及两个候选元素的出现次数 count 1 \textit{count}_1 count1 和 count 2 \textit{count}_2 count2,初始时候选元素和出现次数都是 0 0 0。

遍历数组,当遍历到元素 num \textit{num} num 时,执行如下操作。

  1. 比较 num \textit{num} num 是否和候选元素相等,如果相等则将相应的出现次数加 1 1 1。

    1. 如果 num = majority 1 \textit{num} = \textit{majority}_1 num=majority1,则将 count 1 \textit{count}_1 count1 加 1 1 1。

    2. 否则,如果 num = majority 2 \textit{num} = \textit{majority}_2 num=majority2,则将 count 2 \textit{count}_2 count2 加 1 1 1。

  2. 如果 num \textit{num} num 和两个候选元素都不相等,则判断两个候选元素的出现次数是否为 0 0 0,如果为 0 0 0 则更新候选元素和出现次数。

    1. 如果 count 1 = 0 \textit{count}_1 = 0 count1=0,则将 majority 1 \textit{majority}_1 majority1 更新为 num \textit{num} num,并将 count 1 \textit{count}_1 count1 加 1 1 1。

    2. 否则,如果 count 2 = 0 \textit{count}_2 = 0 count2=0,则将 majority 2 \textit{majority}_2 majority2 更新为 num \textit{num} num,并将 count 2 \textit{count}_2 count2 加 1 1 1。

  3. 如果 num \textit{num} num 和两个候选元素都不相等且两个候选元素的出现次数都大于 0 0 0,则 num \textit{num} num 和两个候选元素抵消,将 count 1 \textit{count}_1 count1 和 count 2 \textit{count}_2 count2 都减 1 1 1。

遍历结束之后,得到两个候选元素。再次遍历数组,统计两个候选元素在数组中的出现次数,当出现次数大于 ⌊ n 3 ⌋ \Big\lfloor \dfrac{n}{3} \Big\rfloor ⌊3n⌋ 时将候选元素添加到结果中。

代码

java 复制代码
class Solution {
    public List<Integer> majorityElement(int[] nums) {
        int majority1 = 0, majority2 = 0;
        int count1 = 0, count2 = 0;
        for (int num : nums) {
            if (num == majority1) {
                count1++;
            } else if (num == majority2) {
                count2++;
            } else if (count1 == 0) {
                majority1 = num;
                count1++;
            } else if (count2 == 0) {
                majority2 = num;
                count2++;
            } else {
                count1--;
                count2--;
            }
        }
        count1 = 0;
        count2 = 0;
        for (int num : nums) {
            if (num == majority1) {
                count1++;
            } else if (num == majority2) {
                count2++;
            }
        }
        List<Integer> majorities = new ArrayList<Integer>();
        int n = nums.length;
        if (count1 > n / 3) {
            majorities.add(majority1);
        }
        if (count2 > n / 3) {
            majorities.add(majority2);
        }
        return majorities;
    }
}

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。需要遍历数组 nums \textit{nums} nums 两次。

  • 空间复杂度: O ( 1 ) O(1) O(1)。

相关推荐
taller_20007 天前
借助Excel实现Word表格快速排序
排序·表格排序·word表格·word排序·随机排序
IU宝13 天前
快速排序的深入优化——三路划分,内省排序(C语言)
c语言·数据结构·算法·排序算法·排序
Tisfy1 个月前
LeetCode 0910.最小差值 II:贪心(排序)-小数大数分界线枚举(思考过程详解)
算法·leetcode·题解·贪心·枚举·思维·排序
DogDaoDao1 个月前
LeetCode 算法:多数元素 c++
数据结构·c++·算法·leetcode·排序
一直学习永不止步2 个月前
LeetCode题练习与总结:H 指数--274
java·数据结构·算法·leetcode·数组·排序·计数排序
bug菌¹2 个月前
滚雪球学MySQL[2.3讲]:MySQL数据过滤与排序详解:WHERE条件、ORDER BY排序与LIMIT分页查询
数据库·mysql·排序·order by·where条件·limit分页
Milkha2 个月前
论文速读记录 - 202409
nlp·论文笔记·排序
希忘auto2 个月前
详解常见排序
java·排序
伟大的车尔尼2 个月前
排序题目:对角线遍历 II
排序
ID_云泽2 个月前
MySQL自定义排序:使用ORDER BY FIELD实现灵活的数据排序
数据库·mysql·排序