面试经典150题——多数元素
目录
题目链接:169. 多数元素 - 力扣(LeetCode)
题目描述
示例
提示:
解法一:哈希表
Java写法:
C++写法:
解法二:它就在那里
Java写法:
C++写法:
解法三:摩尔投票算法
Java写法:
C++写法:
总结
题目链接:169. 多数元素 - 力扣(LeetCode)
注:下述题目描述和示例均来自力扣
题目描述
给定一个大小为 n
的数组 nums
,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋
的元素。
你可以假设数组是非空的,并且给定的数组总是存在多数元素。
示例
示例 1:
输入:nums = [3,2,3] 输出:3
示例 2:
输入:nums = [2,2,1,1,1,2,2] 输出:2
提示:
n == nums.length
1 <= n <= 5 * 10^4
-10^9 <= nums[i] <= 10^9
解法一:哈希表
我们就不要尝试暴力的方式了,因为数据已经到了10^9这个量级了,这个必然是会超时的。
-
初始化数据结构:首先,创建一个
HashMap<Integer, Integer>
来存储数组中每个元素及其对应的出现次数。这个哈希表将作为计数器,用于记录每个元素在数组中出现了多少次。 -
遍历数组:然后,遍历输入的整数数组
nums
。对于数组中的每个元素num
,执行以下操作:- 使用
countMap.getOrDefault(num, 0)
方法检查num
是否已经在哈希表中作为键存在。如果存在,则返回其对应的值(即出现次数);如果不存在,则返回默认值0。 - 将该元素的出现次数加1,并更新哈希表中该元素的计数。这是通过
countMap.put(num, countMap.getOrDefault(num, 0) + 1)
实现的。
- 使用
-
检查多数元素:在遍历过程中,每次更新完哈希表后,都检查当前元素的计数是否已经超过数组长度的一半(即
half
变量)。这是通过if (countMap.get(num) > half)
判断条件实现的。如果找到了这样的元素,说明它已经满足了多数元素的条件(即出现次数大于数组长度的一半),因此直接返回该元素。 -
处理理论上的不可能情况:虽然题目已经保证了多数元素的存在,但为了代码的健壮性,还是保留了一个返回值
-1
。这个返回值在理论上不会被执行到,因为它表示没有找到多数元素。然而,在实际应用中,如果输入不满足题目的条件(即不存在多数元素),这个返回值就变得有用了。不过,在本题的情况下,由于题目已经明确给出了多数元素的存在性,所以这个返回值更多是作为一个安全网或代码健壮性的体现。 -
返回结果:如果在遍历过程中找到了多数元素,就直接返回它。如果遍历结束还没有返回(这在题目给定条件下是不可能发生的),则理论上会执行到返回
-1
的代码行,但实际上不会。
Java写法:
class Solution {
/**
* 找到数组中的多数元素,即出现次数大于数组长度一半的元素。
*
* @param nums 输入的整数数组
* @return 数组中的多数元素
*/
public int majorityElement(int[] nums) {
// 创建一个HashMap来存储每个元素及其出现次数
HashMap<Integer, Integer> countMap = new HashMap<>();
// 获取数组长度
int n = nums.length;
// 计算多数元素至少需要出现的次数
int half = n / 2;
// 遍历数组中的每个元素
for (int num : nums) {
// 如果HashMap中已经存在该元素,则更新其计数
// 如果不存在,则使用getOrDefault方法返回0并加1
countMap.put(num, countMap.getOrDefault(num, 0) + 1);
// 检查当前元素的计数是否已经超过半数
// 如果是,则直接返回该元素,因为它就是多数元素
if (countMap.get(num) > half) {
return num;
}
}
// 理论上,由于题目保证了多数元素的存在,这里不会被执行到
// 但为了代码的健壮性,还是保留一个返回值
// 在实际使用中,可以根据需要抛出异常或进行其他处理
return -1; // 表示没有找到多数元素(但实际上根据题目,这种情况不会发生)
}
}
C++写法:
class Solution {
public:
int majorityElement(vector<int>& nums) {
std::unordered_map<int, int> countMap;
int n = nums.size();
int half = n / 2;
for (int num : nums) {
countMap[num]++;
if (countMap[num] > half) {
return num;
}
}
// 理论上不会执行到这里,因为题目保证了多数元素的存在
// 但为了代码的健壮性,可以抛出一个异常或返回一个特殊值
return 1;
}
};
解法二:它就在那里
-
排序:首先,使用
Arrays.sort(nums)
对数组nums
进行排序。这一步是算法中最耗时的部分,因为排序算法(如快速排序、归并排序等)的时间复杂度通常是O(n log n),其中n是数组的长度。 -
找到多数元素:由于题目保证了多数元素的存在,并且其出现次数大于数组长度的一半,因此在排序后的数组中,多数元素一定会出现在数组的中间位置(即索引为
nums.length / 2
的位置)。这是因为,在排序后的数组中,所有小于多数元素的元素都会排在它前面,所有大于它的元素都会排在它后面,而由于它的出现次数超过了一半,所以它的位置必然在中间。 -
返回结果:直接返回
nums[nums.length / 2]
,即排序后数组的中间元素,作为多数元素。
Java写法:
class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
C++写法:
这个就不用给C++写法了吧,这如此简明扼要了哈哈哈哈。
解法三:摩尔投票算法
摩尔投票算法(Boyer-Moore Voting Algorithm),用于在一个数组中找到出现次数超过数组长度一半的元素(多数元素)。这种算法的核心思想是通过遍历数组来维护一个候选的多数元素及其计数器,从而有效地找到真正的多数元素。
以下是该代码实现思路的详细讲解:
- 初始化候选元素和计数器:
- 选择数组中的第一个元素作为初始的候选元素(
candidate
)。 - 将计数器(
count
)初始化为1,因为我们已经有一个候选元素了。
- 选择数组中的第一个元素作为初始的候选元素(
- 遍历数组:
- 从数组的第二个元素开始遍历(因为第一个元素已经被选为初始候选元素了)。
- 对于每个遍历到的元素,执行以下操作:
- 如果计数器为0,说明当前的候选元素已经不再是多数元素的候选者(因为其他元素已经“抵消”了它的所有出现次数)。此时,将当前遍历到的元素设为新的候选元素,并将计数器重置为1。
- 如果当前遍历到的元素与候选元素相同,说明找到了一个支持候选元素的元素,将计数器加1。
- 如果当前遍历到的元素与候选元素不同,说明找到了一个不支持候选元素的元素,将计数器减1。这可以理解为“抵消”了候选元素的一个出现次数。
- 返回候选元素:
- 遍历结束后,由于题目保证了多数元素的存在,并且其出现次数超过数组长度的一半,因此候选元素最终一定是多数元素。
- 直接返回候选元素作为结果。
这个算法之所以有效,是因为它利用了多数元素的特性:多数元素的数量超过了数组长度的一半。这意味着在遍历过程中,无论我们如何选择候选元素和进行抵消操作,多数元素始终能够“存活”到最后,成为最终的候选元素。
该算法的时间复杂度为O(n),因为我们只遍历了一次数组。空间复杂度为O(1),因为我们只使用了常量级别的额外空间来存储候选元素和计数器。这使得摩尔投票算法成为解决这类问题的非常高效和优雅的方法。
Java写法:
class Solution {
public int majorityElement(int[] nums) {
// 初始化候选元素为数组的第一个元素
int candidate = nums[0];
// 初始化计数器为1
int count = 1;
for (int i = 1; i < nums.length; i++) {
if (count == 0) {
// 计数器为0时,更换候选元素
candidate = nums[i];
count = 1;
} else if (candidate == nums[i]) {
// 候选元素与当前元素相同,计数器加1
count++;
} else {
// 候选元素与当前元素不同,计数器减1
count--;
}
}
// 遍历结束后,candidate就是多数元素
return candidate;
}
}
C++写法:
class Solution {
public:
int majorityElement(vector<int>& nums) {
std::unordered_map<int, int> countMap;
// 初始化候选元素为数组的第一个元素
int candidate = nums[0];
// 初始化计数器为1
int count = 1;
for (int i = 1; i < nums.size(); ++i) {
if (count == 0) {
// 计数器为0时,更换候选元素
candidate = nums[i];
count = 1;
} else if (candidate == nums[i]) {
// 候选元素与当前元素相同,计数器加1
count++;
} else {
// 候选元素与当前元素不同,计数器减1
count--;
}
}
// 遍历结束后,candidate就是多数元素
return candidate;
}
};
总结
三种解法,大家好好看看吧~~~~