1. 问题回顾
小R从班级中抽取了一些同学,每位同学都会给出一个数字。已知在这些数字中,某个数字的出现次数超过了数字总数的一半。现在需要你帮助小R找到这个数字。
测试样例
样例1:
输入:
array = [1, 3, 8, 2, 3, 1, 3, 3, 3]
输出:3
样例2:
输入:
array = [5, 5, 5, 1, 2, 5, 5]
输出:5
样例3:
输入:
array = [9, 9, 9, 9, 8, 9, 8, 8]
输出:9
2. 思考与题解
2.1 暴力法
使用暴力法来解决这个问题,我们可以通过两层循环来统计每个元素的出现次数,然后找到出现次数大于 ⌊ n/2 ⌋ 的元素。
详细步骤:
- 遍历数组:外层循环遍历数组中的每个元素。
- 统计出现次数:内层循环统计当前元素在数组中出现的次数。
- 检查多数元素:如果某个元素的出现次数大于
⌊ n/2 ⌋,则返回该元素。
#include <vector>
int majorityElement(std::vector<int>& nums) {
int n = nums.size();
int majorityCount = n / 2;
for (int i = 0; i < n; ++i) {
int count = 0;
for (int j = 0; j < n; ++j) {
if (nums[j] == nums[i]) {
count++;
}
}
if (count > majorityCount) {
return nums[i];
}
}
// 由于题目保证存在多数元素,因此不会执行到这里
return -1;
}
int main() {
std::vector<int> nums1 = {3, 2, 3};
std::vector<int> nums2 = {2, 2, 1, 1, 1, 2, 2};
std::cout << "Example 1: " << majorityElement(nums1) << std::endl; // 输出 3
std::cout << "Example 2: " << majorityElement(nums2) << std::endl; // 输出 2
return 0;
}
复杂度分析:
时间复杂度
- 外层循环:外层循环的时间复杂度是
O(n),其中n是数组的长度。 - 内层循环:内层循环的时间复杂度也是
O(n)。
因此,总的时间复杂度是 O(n^2)。
空间复杂度
- 变量:除了几个变量(如
count和majorityCount)外,没有使用额外的空间。
因此,总的空间复杂度是 O(1)。
2.2 排序法
基本思路:
- 排序数组:首先对数组进行排序。
- 找到多数元素:由于多数元素的出现次数大于
⌊ n/2 ⌋,那么在排序后的数组中,位于中间位置的元素(即nums[n/2])一定是多数元素。
详细步骤:
- 排序数组:使用 C++ 的
std::sort函数对数组进行排序。 - 返回中间元素:排序后,数组的中间元素
nums[n/2]就是多数元素。
#include <vector>
#include <algorithm>
int majorityElement(std::vector<int>& nums) {
// 对数组进行排序
std::sort(nums.begin(), nums.end());
// 返回中间位置的元素
return nums[nums.size() / 2];
}
int main() {
std::vector<int> nums1 = {3, 2, 3};
std::vector<int> nums2 = {2, 2, 1, 1, 1, 2, 2};
std::cout << "Example 1: " << majorityElement(nums1) << std::endl; // 输出 3
std::cout << "Example 2: " << majorityElement(nums2) << std::endl; // 输出 2
return 0;
}
复杂度分析:
时间复杂度
- 排序:排序的时间复杂度是
O(n log n),其中n是数组的长度。这是因为在最坏情况下,排序算法需要对数组中的每个元素进行比较和交换操作。 - 访问中间元素:访问中间元素的时间复杂度是
O(1),因为直接通过索引访问数组元素是一个常数时间操作。
因此,总的时间复杂度是 O(n log n)。
空间复杂度
- 排序:排序的空间复杂度取决于所使用的排序算法。对于
std::sort来说,它通常是O(log n)的空间复杂度,因为它是基于快速排序、堆排序和插入排序的混合算法。快速排序的空间复杂度是O(log n),而堆排序和插入排序的空间复杂度是O(1)。 - 其他变量:除了排序所需的空间外,没有使用额外的空间。
2.3 哈希表法
详细步骤:
- 创建哈希表:使用
std::unordered_map来存储每个元素及其出现次数。 - 统计元素出现次数:遍历数组,将每个元素及其出现次数存储在哈希表中。
- 找到多数元素:遍历哈希表,找到出现次数大于
⌊ n/2 ⌋的元素。
#include <vector>
#include <unordered_map>
int majorityElement(std::vector<int>& nums) {
std::unordered_map<int, int> countMap;
int majorityCount = nums.size() / 2;
// 统计每个元素的出现次数
for (int num : nums) {
countMap[num]++;
// 如果某个元素的出现次数大于 ⌊ n/2 ⌋,则返回该元素
if (countMap[num] > majorityCount) {
return num;
}
}
// 由于题目保证存在多数元素,因此不会执行到这里
return -1;
}
int main() {
std::vector<int> nums1 = {3, 2, 3};
std::vector<int> nums2 = {2, 2, 1, 1, 1, 2, 2};
std::cout << "Example 1: " << majorityElement(nums1) << std::endl; // 输出 3
std::cout << "Example 2: " << majorityElement(nums2) << std::endl; // 输出 2
return 0;
}
复杂度分析:
时间复杂度
- 遍历数组:遍历数组的时间复杂度是
O(n),其中n是数组的长度。 - 哈希表操作:哈希表的插入和查找操作的平均时间复杂度是
O(1)。
因此,总的时间复杂度是 O(n)。
空间复杂度
- 哈希表:哈希表的空间复杂度是
O(n),因为最坏情况下,哈希表需要存储数组中的所有不同元素。
因此,总的空间复杂度是 O(n)。
2.4 摩尔投票法
基本原理
摩尔投票法(Boyer-Moore Voting Algorithm)是一种用于在数组中找到出现次数大于 ⌊ n/2 ⌋ 的多数元素的高效算法。其核心思想是通过抵消不同元素的计数来找到多数元素。
- 多数元素的定义:在一个大小为
n的数组中,多数元素是指出现次数大于⌊ n/2 ⌋的元素。由于多数元素的出现次数大于数组长度的一半,因此在数组中,多数元素的数量一定比其他所有元素的数量之和还要多。 - 抵消计数:摩尔投票法的核心思想是通过抵消不同元素的计数来找到多数元素。具体来说,算法维护一个票数计数器
vote和一个候选元素Mode。遍历数组时,如果当前元素与候选元素相同,则计数器加 1;如果不同,则计数器减 1。当计数器减到 0 时,说明当前候选元素的计数已经被抵消完,需要重新选择一个新的候选元素。 - 最终候选元素:由于多数元素的出现次数大于
⌊ n/2 ⌋,因此在遍历结束后,候选元素candidate一定是多数元素。
实现步骤
详细步骤:
-
初始化计数器和候选元素:初始化一个计数器
vote和一个候选元素Mode。 -
遍历数组:遍历数组中的每个元素。
- 如果计数器
vote为 0,则将当前元素设为候选元素,并将计数器设为 1。 - 如果当前元素等于候选元素,则计数器加 1。
- 如果当前元素不等于候选元素,则计数器减 1。
- 如果计数器
-
返回候选元素:遍历结束后,候选元素即为多数元素。
#include <iostream>
#include <vector>
using namespace std;
int solution(vector<int> array) {
//Mole Vote
int Mode = array[0];
int vote = 0;
for (int i = 0; i < array.size(); i++) {
if (array[i] == Mode) {
vote += 1;
} else {
vote -= 1;
}
if (vote == 0) {
Mode = array[i + 1];
}
}
return Mode;
}
复杂度分析:
时间复杂度
- 遍历数组:遍历数组的时间复杂度是
O(n),其中n是数组的长度。
因此,总的时间复杂度是 O(n)。
空间复杂度
- 变量:除了几个变量(如
vote和Mode)外,没有使用额外的空间。
因此,总的空间复杂度是 O(1)。