题目:
Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋
times.
You may assume that the array is non-empty and the majority element always exist in the array.
Example 1:
Input: [3,2,3]
Output: 3
Example 2:
Input: [2,2,1,1,1,2,2]
Output: 2
这道题也是之前做过的题,但是看到它的第一反应依旧是two sum带来的hashMap计算次数方法,也想到了排序取中位数的方法,不太记得之前是哪道题做过类似的了,好像是剑指上的但是可能之前准备面试临时抱佛脚时做的也没记录。看了下discussion和solution发现居然除了brute force以外还有六种解法,震惊!
1. HashMap法
每次遍历到一个数字,就把它在hashMap中的count+1,如果发现某个count超过了数组大小的一半,就return它。
代码如下,时间复杂度O(n),24ms,58.55%,空间复杂度O(n),11.2M,36.48%:
/*
* @lc app=leetcode id=169 lang=cpp
*
* [169] Majority Element
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int> count;
for (int i = 0; i < nums.size(); i++) {
count[nums[i]]++;
if (count[nums[i]] > nums.size() / 2) {
return nums[i];
}
}
return 0;
}
};
比较有收获的一点就是,unordered_map居然不需要手动初始化就默认为0,真是太方便辽!
2022.11.7 Java
class Solution {
public int majorityElement(int[] nums) {
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
map.put(num, map.getOrDefault(num,0) + 1);
}
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() > nums.length / 2) {
return entry.getKey();
}
}
return 0;
}
}
2. 排序取中位数法
第二种方法是排序法,由于出现次数最多的元素的出现次数比其他所有元素出现的次数加起来还多(注意这里是比其他的加起来还多,而不是可能相等,比如[1,1,2,3]这种情况是不存在的),因此如果对整个数组进行一次排序,那么排列在中间(如果是偶数的话就是后半部分的第一个)元素一定会是出现最多的元素。
另外在discussion里看到大佬们直接采用的是部分排序函数nth_element,查了一下,这个函数可以直接返回第n个大小的元素排序正确的数组,第一个参数是begin,第二个参数是要求的第n个(比如要求第4个则要nums.begin() + 3),最后一个参数是end。https://blog.csdn.net/ihadl/article/details/7400929 这篇博客对vector的各种排序归纳得很好。
代码如下,时间复杂度O(nlogn),28ms,38.88%,空间复杂度O(1),11.1M,60.16%:
/*
* @lc app=leetcode id=169 lang=cpp
*
* [169] Majority Element
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
sort(nums.begin(), nums.end());
// nth_element(nums.begin(), nums.begin() + nums.size() / 2, nums.end());
return nums[nums.size() / 2];
}
};
2022.11.7 Java
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
3. 随机选择法
这个方法真是太骚了哈哈哈,它的本意就是,设置一个随机数,随机抽取数组里面的一个元素,然后遍历整个数组看它出现的次数有多少。这个方法可行的原因在于,我们最终要找的那个数字在整个数组中出现的次数超过了一半,因此随机抽取一个元素,它也大概率就是我们要找的数字。
这里复习了一下C++中随机数的生成方法,首先是srand(unsigned(time(NULL)))生成随机种子,然后采用rand()获取随机数。
代码如下,最坏时间复杂度O(∞),因为可能死循环在挑不到正确的元素上,16ms,98.21%,空间O(1),11M,69.31%:
/*
* @lc app=leetcode id=169 lang=cpp
*
*
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate;
srand(unsigned(time(NULL)));
while (true) {
int count = 0;
candidate = nums[rand() % nums.size()];
for (int i = 0; i < nums.size(); i++) {
if (nums[i] == candidate) {
count++;
}
if (count > nums.size() / 2) {
return candidate;
}
}
}
return 0;
}
};
4. 多数投票算法(Boyer-Moore Algorithm)
这个神秘的多数投票算法真的非常巧妙,也是利用了要找的数字出现次数超过一半这个性质。我们从头开始遍历这个数组,选出一个candidate,并用一个count来记录。count初始化为0,如果当前的数字和选出的candidate一样,那就count++,而如果不一样,就count--,当count减为0的时候,就要换一个candidate了。这个算法的正确性在于,我们要找的数字一定是比其他所有数字出现的次数都多,就算是遇到了别的数字,那么count--就相当于拿别的数字和它相抵消,抵消到最后总会是最多的数字剩下,就return它就好了。
这篇文章讲的比较详细,可以一看:多数投票算法(Boyer-Moore Algorithm)详解_kimixuchen的博客-CSDN博客_多数投票算法
代码如下,时间复杂度O(n),20ms,92.54%;空间复杂度O(1),11.3M,29.6%:
/*
* @lc app=leetcode id=169 lang=cpp
*
* [169] Majority Element
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
int candidate;
int count = 0;
for (int i = 0; i < nums.size(); i++) {
if (count == 0) {
candidate = nums[i];
}
if (candidate != nums[i]) {
count--;
}
else {
count++;
}
}
return candidate;
}
};
5. 分治法
虽然自己刚开始没有想到分治法,但是看了知道用分治法以后,大概也能自己把代码写得七七八八了,主要是最后归并的处理还不是很熟练。归并首先计算出左边和右边的多数元素,如果左右相同就可以随便return,而如果不同就需要重新计算左边和右边的多数元素在整串数组中谁多谁少并return多的了。这里有一点需要注意的是,在最后使用count()函数进行计数时,后面的iterator应该是nums.begin() + end + 1,注意+1,应该是因为在count中的第二个参数是不包含的,所以必须+1,让它包含这个数组。还是对STL不熟悉啊orz
代码如下,时间32ms,18.18%,空间11.5M,5.04%。时间复杂度O(NlogN),每次递归产生两个长度为n/2的子序列,并进行两次长度为n的扫描,合起来T(n)=2T(n/2)+2n,由主定理可以化简成O(NlogN);空间复杂度O(logN),因为递归会占用栈空间,每次计算一半,就产生了logN高度的递归树。
/*
* @lc app=leetcode id=169 lang=cpp
*
*
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
return findMajor(nums, 0, nums.size() - 1);
}
int findMajor(vector<int>& nums, int begin, int end) {
if (begin == end) {
return nums[begin];
}
int mid = (begin + end) / 2;
int left_major = findMajor(nums, begin, mid);
int right_major = findMajor(nums, mid + 1, end);
if (left_major == right_major) {
return left_major;
}
return count(nums.begin() + begin, nums.begin() + end + 1, left_major) > count(nums.begin() + begin, nums.begin() + end + 1, right_major) ? left_major : right_major;
}
};
6. 位操作
这个方法也是一种神秘操作,它是对所有数字中的每一位进行统计的,统计出每一位上是0居多还是1居多,最后要返回的数字就是所有位数合起来就是了。这里需要注意的一点就是,用于表示每一位的mask要定义成unsigned int,不然如果是有符号数的话最后的时候左移就会溢出。感觉自己对有符号数无符号数这些的掌握还是不牢固,有机会要复习一下。
代码如下,时间复杂度O(n),24ms,57.98%,空间复杂度O(1),11.2M,41.12%:
/*
* @lc app=leetcode id=169 lang=cpp
*
*
*/
class Solution {
public:
int majorityElement(vector<int>& nums) {
int result = 0;
unsigned int mask = 1;
for (int i = 0; i < 32; i++) {
int bits = 0;
for (int i = 0; i < nums.size(); i++) {
if (nums[i] & mask) {
bits++;
}
}
if (bits > nums.size() / 2) {
result |= mask;
}
mask <<= 1;
}
return result;
}
};