「这是我参与2022首次更文挑战的第17天,活动详情查看:2022首次更文挑战」。
题目描述🌍
给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例 1:
输入:[3,2,3]
输出:3
示例 2:
输入:[2,2,1,1,1,2,2]
输出:2
进阶:
- 尝试设计时间复杂度为 、空间复杂度为 的算法解决此问题。
计数法🎮
解题思路
统计数组中每一个元素出现的次数,从中找出次数 > ⌊ n/2 ⌋ 的元素。
代码
Java
class Solution {
public int majorityElement(int[] nums) {
Map<Integer, Long> map = Arrays.stream(nums).boxed().collect(Collectors.groupingBy(Function.identity(), Collectors.counting()));
int count = nums.length >> 1;
// Map 无法使用增强for循环; Map.Entry<>为Map提供一种遍历方式
for (Map.Entry<Integer, Long> entry : map.entrySet()) {
if (entry.getValue() > count)
return entry.getKey();
}
return -1;
}
}
以上统计个数调用 IntStream 库函数,如下是不调库的写法:
class Solution {
public int majorityElement(int[] nums) {
// count every element
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) {
// Integer default-value: null(not 0)
if (!map.containsKey(num)) {
map.put(num, 1);
} else {
map.put(num, map.get(num) + 1);
}
}
// find the majorityNum [which > n/2]
int half = nums.length / 2;
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
if (entry.getValue() > half)
return entry.getKey();
}
// unreachable: because the majority is existent absolutely
return -1;
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
unordered_map<int, int> counts;
int majority = 0;
int mediant = 0;
for (int num: nums) {
++counts[num];
// 边计数边维护临时的多数元素, 这样可避免最后再遍历一次该哈希映射
// 当且仅当一定存在多数元素时才有效
if (counts[num] > mediant) {
majority = num;
mediant = counts[num];
}
}
return majority;
}
};
时间复杂度:
空间复杂度:
排序取中间元素🔮
解题思路
排序后相同元素一定是相邻的,所以数组排序后中间的元素一定是“多数元素”。
代码
Java
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length >> 1];
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
sort(nums.begin(), nums.end());
return nums[nums.size() / 2];
}
};
时间复杂度:
空间复杂度:
对拼消耗法🚀
解题思路
直接从头到尾遍历一次数组,不同元素逐个相消,相同元素个数 +1,消除到最后一定会剩余 个相同的元素,即多数元素。
⭐注:题目明确说到给定的数组总是存在多数元素,否则最后获取的数需要校验是否大于 ⌊ n / 2 ⌋。
代码
Java
class Solution {
public int majorityElement(int[] nums) {
int count = 1;
int num = nums[0];
int length = nums.length;
for (int i = 1; i < length; i++) {
if (nums[i] == num) {
count++;
} else {
count--;
if (count < 1) {
num = nums[i + 1];
}
}
}
return num;
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
int count = 1;
int num = nums[0];
int length = nums.size();
for (int i = 1; i < length; ++i) {
if (nums[i] == num) {
count++;
} else {
count--;
if (count < 1) {
num = nums[i + 1];
}
}
}
return num;
}
};
时间复杂度:
空间复杂度:
随机化🎴
解题思路
因为数组中超过 的数都是『同一个』多数元素,所以在数组中随机取值,验证后很大概率就是多数元素。当然最坏情况下,就是一直找不到多数元素,然而平均情况下的时间是线性的。
代码
Java
class Solution {
// 验证随机数是否为多数元素
public boolean verifyRandomNumber(int randomNum, int[] nums) {
int count = 0;
int length = nums.length;
for (int i = 0; i < length; i++) {
if (randomNum == nums[i])
count++;
}
return count > length / 2;
}
// 生成给定区间内的随机数
public int generateByRange(int min, int max, int[] nums) {
Random random = new Random();
// defaultRandomRange: [0,value)
return random.nextInt(max - min) + min;
}
// 随机化
public int majorityElement(int[] nums) {
while (true) {
int randomNum = nums[generateByRange(0, nums.length, nums)];
if (verifyRandomNumber(randomNum, nums))
return randomNum;
}
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
while (true) {
int candidate = nums[rand() % nums.size()];
int count = 0;
for (int num: nums)
if (candidate == num)
count++;
if (count > nums.size() / 2)
return candidate;
}
}
};
时间复杂度:,验证元素的时间。
空间复杂度:
分治法🥏
解题思路
如果 x 是数组的多数元素,一旦将数组划分为两个区间,x 至少是其中一个区间的众数。这样就可以使用分治法加以解决;分别从左/右半区间选出众数 x1 和 x2,其中在整个区间中个数较多的元素即为多数元素。
⭐代码解释:若左右两个区间的“众数”值一致,则合并区间后的“多数元素”就是该数;若值不一致,则分别计算 leftEle 与 rightEle 这两个值在区间合并后的个数,返回个数较多的那个【个数一致则无所谓哪个】,显然该数就是合并区间的多数元素。
代码
Java
class Solution {
public int majorityElement(int[] nums) {
return getMajorityEle(nums, 0, nums.length - 1);
}
// 分治法求多数元素
public int getMajorityEle(int[] nums, int low, int high) {
if (low == high) {
return nums[low];
}
int middle = (low + high) / 2;
int leftEle = getMajorityEle(nums, low, middle);
int rightEle = getMajorityEle(nums, middle + 1, high);
// 若左右区间的"众数"相同, 则直接返回
if (leftEle == rightEle) {
return leftEle;
}
// 左右区间的"众数"不同, 那么就返回合并区间后二者中个数较多的那个
int leftCount = countInRange(nums, leftEle, low, high);
int rightCount = countInRange(nums, rightEle, low, high);
return leftCount > rightCount ? leftEle : rightEle;
}
// 统计指定范围内 target 的个数
public int countInRange(int[] nums, int target, int low, int high) {
int count = 0;
for (int i = low; i <= high; i++) {
if (nums[i] == target)
count++;
}
return count;
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
return getModalNumber(nums, 0, nums.size() - 1);
}
int getModalNumber(vector<int> &nums, int low, int high) {
if (low == high)
return nums[low];
int middle = (low + high) / 2;
int left_ele = getModalNumber(nums, low, middle);
int right_ele = getModalNumber(nums, middle + 1, high);
if (left_ele == right_ele) {
return left_ele;
}
int left_count = getCount_in_range(nums, left_ele, low, high);
int right_count = getCount_in_range(nums, right_ele, low, high);
return left_count > right_count ? left_ele : right_ele;
}
int getCount_in_range(vector<int> &nums, int target, int low, int high) {
int count = 0;
for (int i = low; i <= high; ++i) {
if (nums[i] == target)
++count;
}
return count;
}
};
🎈分治算法时间复杂度求解公式:
时间复杂度:
空间复杂度:,递归使用额外栈空间。
摩尔投票算法🍤
解题思路
如果将多数元素替换为 1,而非其余元素替换为 -1,那么将它们全部加起来一定会大于 0。
借鉴上述思想,但我们事先不知道多数元素是哪个,所以我们让数组中任意不相同的两个元素相互抵消(也会存在非多数元素与非多数元素抵消的情况),最后剩下的那些 (个) 元素一定是相同。至于候选元素是否为多数元素仍需验证(是否 > n/2),但题目明确说到给定数组一定存在多数元素,所以无需考虑校验问题。
为什么说不一定,举个反例:
[1, 1, 1, 2, 2, 2, 3],返回3,错误。
其实认真一看,「摩尔投票算法」的思想不就是方法三「对拼消耗法」吗?!
写成 2 种方法是因为我也是今天才知道摩尔投票算法这个新玩意呢!方法三是老师曾经教过的方法,自己稍微总结下就贴出来了。
代码
Java
class Solution {
// Boyer-Moore 算法
public int majorityElement(int[] nums) {
int count = 0;
Integer candidate = null;
for (int num : nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
}
C++
class Solution {
public:
int majorityElement(vector<int> &nums) {
int count = 0;
int candidate;
for (int num: nums) {
if (count == 0) {
candidate = num;
}
count += (num == candidate) ? 1 : -1;
}
return candidate;
}
};
时间复杂度:
空间复杂度:
牛刀小试
⭐理解了摩尔投票算法后,不妨试试这道进阶题:229. 求众数 Ⅱ
最后🌅
该篇文章为 「LeetCode」 系列的 No.16 篇,在这个系列文章中:
- 尽量给出多种解题思路
- 提供题解的多语言代码实现
- 记录该题涉及的知识点
👨💻争取做到 LeetCode 每日 1 题,所有的题解/代码均收录于 「LeetCode」 仓库,欢迎随时加入我们的刷题小分队!