Majority Element && Majority Element II

Majority Element

https://leetcode.com/problems/majority-element/
Difficulty: Easy

查找数组的多数元素(majority element)

多数元素为数组中出现次数多于 n/2 的元素。假设数组非空且多数元素一定存在

LeetCode的解答中给出了七种思路

第一种是Brute force solution,时间复杂度为 O(n2) ,顾名思义,遍历数组,依次判断每个元素是否为多数元素

第二种是Hash table,时间复杂度为 O(n) ,空间复杂度也为 (n) ,对数组中的每个元素计数,大于 n/2 时即为所求结果

第三种是Sorting,时间复杂度为 O(nlogn) ,找到第 n/2 个元素即可

第四种是Randomization,平均时间复杂度为 O(n) ,最坏情况为无穷大。随机选一个元素,判断其是否为多数元素。由于选中多数元素的概率大于 1/2 ,尝试次数的期望 <2 <script type="math/tex" id="MathJax-Element-10"><2</script>

// Runtime: 20 ms
#include <cstdlib>
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int size = nums.size();
        while (true) {
            int r = nums[rand() % size];
            int count = 0;
            for (int i = 0; i < size; i++) {
                if (r == nums[i]) {
                    count++;
                }
            }
            if (count > size >> 1) {
                return r;
            }
        }
    }
};

第五种是Divide and conquer,时间复杂度为 O(nlogn) ,将数组分成两半,分别递归查找这两部分的多数元素,若两者相同,则为多数元素,若不同,则其中之一必为多数元素,判断其一即可

第六种是Moore voting algorithm,时间复杂度为 O(n) ,这个算法可能是最为巧妙的一种方法了吧。维护一个candidate和一个counter,counter初始化为0,之后遍历整个数组,如果counter为0,则 candidate 设置为当前元素,并且 counter 设置为 1,如果 counter 不为 0,根据当前元素是否为 candidate 来递增或递减 counter。若数组非空,实际中,也可以直接把 candidate 赋值为数组的第一个元素。参考Majority Element II,把 candidate 设置为任意值,并且 counter 设为 0 是最自然的一种思路了。可能这个算法的全称是这个吧。Boyer-Moore Majority Vote Algorithm

// Runtime: 16 ms
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int maj = nums[0], count = 1;
        for (int i = 1; i < nums.size(); i++) {
            if (count == 0) {
                maj = nums[i];
                count++;
            }
            else if (maj == nums[i]) {
                count++;
            }
            else {
                count--;
            }
        }
        return maj;
    }
};

第七种是Bit manipulation,时间复杂度为 O(n) ,需要32个计数器,每个计数器记录所有数组某一位的 1 的数目,由于多数元素一定存在,那么 1 的数目和 0 的数目必然不同,多者即为多数元素那一位的取值

Majority Element II

https://leetcode.com/problems/majority-element-ii/
Difficulty: Medium

查找数组中出现次数多于 n/3 的元素

类似于 Majority Element 中的第六种算法Boyer-Moore Majority Vote Algorithm,由于最多有两个可能的元素,所以我们使用两个 candidate,每个 candidate 对应一个 counter。Majority Element 中若两个元素不同,则去除这两个元素并不影响剩余数组的多数元素。类似的,在本题中,如果去除三个不同的元素,并不影响剩余数组的出现次数多于 n/3 的元素。先判断当前元素是否与 candidate 相匹配,若不匹配,则判断是否要更新 candidate,若也不需要更新,则已经获取了三个不同的元素,即当前元素和两个 candidate,去除的方式是两个 counter 同时减一。

需要注意的是,循环中判断的顺序很重要,需要先判断当前元素是否与两个 candidate 之一相匹配,若均不匹配,再判断 counter。这就需要考虑,最初的 candidate 需要如何确定。即便数组的长度大于等于2,依旧不能类似 Majority Element 那样,使用数组的前两个元素作为两个 candidate,因为数组的前两个元素可能是相同的。其实解决办法也很简单,只要给两个 candidate 赋予不同的初值,并且两个 counter 的初值均为 0 即可。

如果先判断 counter,则有可能出现两个 candidate 相同的情况。如测试用例 [2,2] [1,1,1,1,2,1]

class Solution {
public:
    vector<int> majorityElement(vector<int>& nums) {
        vector<int> ret;
        int n1 = 0, n2 = 1, count1 = 0, count2 = 0;
        for (auto n: nums) {
            if (n == n1) {
                count1++;
            } else if (n == n2) {
                count2++;
            } else if (count1 == 0) {
                n1 = n;
                count1 = 1;
            } else if (count2 == 0) {
                n2 = n;
                count2 = 1;
            } else {
                count1--;
                count2--;
            }
        }
        if (count1 != 0 && validateME(nums, n1)) {
            ret.push_back(n1);
        }
        if (count2 != 0 && validateME(nums, n2)) {
            ret.push_back(n2);
        }
        return ret;
    }
    bool validateME(vector<int>& nums, int val) {
        int count = 0;
        for (auto n: nums) {
            if (n == val) {
                count++;
            }
        }
        if (count > nums.size() / 3) {
            return true;
        } else {
            return false;
        }
    }
};

扩展

Boyer-Moore Majority Vote Algorithm 中给出了两个扩展,一是使用更少的比较次数,一是如何并行执行这个算法

Fewer Comparisons

在不确定多数元素是否存在的情况下,使用 Boyer-Moore 算法找到的 candidate 还需要遍历一次数组对其出现次数进行计数。在最坏情况下需要比较 2N 次(出现 n/2+1 次时停止即可,所以是最坏情况)。这部分的算法只需要 3N/22 次比较,但是需要额外的空间( N )。

核心思想是重新排列元素确保相邻元素不同。

第一遍,维护一个空的 rearranged list 和一个空的 bucket。遍历数组,并与 list 的最后一个元素相比,若相同,则把该元素放入 bucket 中,若不同,则放入 list 中,然后取 bucket 的一个元素也放入 list 中。遍历完成后,list 的最后一个元素即为备选的 candidate

第二遍,一次用 list 的最后一个元素与 candidate 比较,若相同,则删除 list 的最后两个元素,若不同,则删除 list 的最后一个元素和 bucket 中的一个元素。如果移除了 list 的所有元素并且 bucket 不空,则 candidate 是多数元素。(很多边界条件需要考虑,但大体思想如此。举一个实际的例子基本就理解了这个算法。比如 5 5 0 0 5,最后 list 剩余 5,而 bucket 为空,这也算是 candidate 为多数元素的一种情况)

举个例子,5(1) 5 (2) 0(1) 0(2) 0(3) 5(3) 0(4) 0(5) 5(4),这里只给出第一遍遍历的情况,第二遍按照思想,很容易走通

iterator list bucket
1 5(1)
2 5(1) 5(2)
3 5(1) 0(1) 5(2)
4 5(1) 0(1) 5(2) 0(2)
5 5(1) 0(1) 5(2) 0(2) 0(3)
6 5(1) 0(1) 5(2) 0(2) 5(3) 0(3)
7 5(1) 0(1) 5(2) 0(2) 5(3) 0(3) 0(4)
8 5(1) 0(1) 5(2) 0(2) 5(3) 0(3) 0(4) 0(5)
9 5(1) 0(1) 5(2) 0(2) 5(3) 0(3) 5(4) 0(4) 0(5)

Distributed Boyer-Moore

保留每个处理结果的 candidate 和 counter,对所有的结果再执行一次 Boyer-Moore 算法。需要注意的是,这次不是单纯的增减一了,而是根据需要比较元素的 counter 来进行处理。这里的代码引自原文,虽然是用 Python 写的,但是一目了然。

candidate = 0
count = 0
for candidate_i, count_i in parallel_output:
  if candidate_i = candidate
    count += count_i
  else if count_i > count:
    count = count_i - count
    candidate = candidate_i
  else
    count = count - count_i

with n/3

对于 Fewer Comparisons,需要维护一个空的 rearranged list 和两个空的 bucket。bucket 有点 candidate的感觉,因为 candidate 有两个,所以需要两个 bucket

对于 Distributed Boyer-Moore,最开始思考时,对于 candidate 不同的判断感觉非常乱,需要对很多情况进行比较的样子,但后来发现,对于 counter的减一或 counter 为 0 时新的赋值,本质上只是记录当前出现最多的元素的次数。考虑这样的数据结构

struct ME {
    int val;
    int count;
};

再回顾一遍比较的思路,比较candidate与当前元素,若两者的val相同,则count增一,否则,若count不为0,则减一,若为零,则新的 candidate 为当前元素。注意到当前元素的count可以看做 1。因此,当 val 不同时,新的candidate为原 candidate和当前元素中 count大的那一个,新的count值为两者之差。这一发现非常重要,在考虑 Distributed Boyer-Moore 时,使用这一思路,对于 candidate 与当前元素不同的情况的处理将会非常清晰。对于 n/3 的情况,若当前元素与 candidate1 和 candidate2 均不同,则新的 candidate 为三者中 count较大的两个,而新的count为这两个的 count 减去最小的那个元素的 count

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值