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
// 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/2?2次比较,但是需要额外的空间(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
版权声明:本文为博主原创文章,未经博主允许不得转载。