题目
有1亿个正整数,每个数最多8位,即不超过99999999,
现在有一台电脑,只有100M内存,也就是说把所有数全都存进去是不现实的,
如何找出这些数中不重复的数有多少个
这道题的实现思路是什么,请讲解一下
并用C++进行实现,并用 简体中文 回复我的问题
实现方法
-
使用
bitset
存储出现次数:- 定义两个
bitset
,分别记录每个数是否出现过一次和是否出现过两次。 seen_once
:如果某个数出现过一次,则对应位置为1
,否则为0
。seen_twice
:如果某个数出现过两次,则对应位置为1
,否则为0
。
- 定义两个
-
遍历测试数组,更新
bitset
:- 遍历输入的
test_numbers
数组,对于每一个数,检查它在seen_once
和seen_twice
中的状态:- 如果这个数之前没有出现过(即
seen_once[num]
和seen_twice[num]
都为0
),则将其标记为seen_once
(即第一次出现)。 - 如果这个数已经在
seen_once
中,但不在seen_twice
中,说明是第二次出现,于是将其从seen_once
中移除,并将其标记为seen_twice
。
- 如果这个数之前没有出现过(即
- 遍历输入的
-
统计出现一次的数:
- 遍历
seen_once
的每一位,找出所有seen_once
中为1
的数,这些数就是只出现一次的数。
- 遍历
C++代码
使用bitset
#include <iostream>
#include <vector>
#include <bitset>
const int MAX_NUM = 99999999; // 数字范围
const int BITMAP_SIZE = MAX_NUM + 1;
std::bitset<BITMAP_SIZE> seen_once; // 标记出现过一次的数
std::bitset<BITMAP_SIZE> seen_twice; // 标记出现过两次的数
int main() {
// 测试数组,包含一些重复和不重复的数
std::vector<int> test_numbers = {1, 2, 3, 4, 5, 3, 2, 6, 7, 8, 9, 5, 10, 11, 12, 13, 14, 15, 1};
// 遍历每一个数,更新位图
for (int num : test_numbers) {
if (!seen_once[num] && !seen_twice[num]) {
// 第一次出现
seen_once.set(num);
} else if (seen_once[num] && !seen_twice[num]) {
// 第二次出现
seen_once.reset(num); // 从seen_once中移除
seen_twice.set(num); // 加入seen_twice
}
}
// 统计只出现一次的数
int unique_count = 0;
std::cout << "不重复的数: ";
for (int i = 1; i <= MAX_NUM; ++i) {
if (seen_once[i]) {
std::cout << i << " ";
unique_count++;
}
}
std::cout << "\n不重复的数有 " << unique_count << " 个" << std::endl;
return 0;
}
不使用bitset
#include <iostream>
#include <vector>
using namespace std;
int main() {
// 假设输入的数已经存储在一个文件中,逐行读取
// 这里假设输入的数已经存储在一个vector中,方便演示
vector<int> numbers = {1, 2, 2, 2, 2, 1, 2, 2, 2}; // 示例输入
// 创建一个位图,大小为100000000 * 2位,即25M内存
vector<unsigned int> bitmap(100000000 / 16, 0); // 每个unsigned int 32位,每2位表示一个数
// 遍历所有数,记录每个数的状态
for (int num : numbers) {
int index = num / 16; // 计算在bitmap中的索引
int offset = (num % 16) * 2; // 计算在32位中的偏移量
unsigned int mask = 3 << offset; // 创建掩码,3的二进制是11,表示两位
unsigned int state = (bitmap[index] & mask) >> offset; // 获取当前状态
if (state == 0) {
bitmap[index] |= (1 << offset); // 设置为01
} else if (state == 1) {
bitmap[index] |= (2 << offset); // 设置为10
}
}
// 统计真正不重复的数的个数
int uniqueCount = 0;
for (int num = 0; num < 100000000; num++) {
int index = num / 16; // 计算在bitmap中的索引
int offset = (num % 16) * 2; // 计算在32位中的偏移量
unsigned int mask = 3 << offset; // 创建掩码,3的二进制是11,表示两位
unsigned int state = (bitmap[index] & mask) >> offset; // 获取当前状态
if (state == 1) {
uniqueCount++;
}
}
cout << "真正不重复的数的个数: " << uniqueCount << endl;
return 0;
}
代码分析
时间复杂度
- 遍历输入数组:
- 遍历
test_numbers
中的每个数,每次操作都是常数时间(O(1))的bitset
操作(检查和设置位),因此这部分的时间复杂度为O(n)
,其中n
是test_numbers
数组的大小。
- 遍历
- 统计出现一次的数:
- 遍历所有可能的数(从
1
到MAX_NUM
,即99999999
个数),这部分时间复杂度为O(MAX_NUM)
。
- 遍历所有可能的数(从
因此,整个算法的时间复杂度为O(n + MAX_NUM)
,即线性复杂度。
空间复杂度
bitset
占用空间:seen_once
和seen_twice
各需要MAX_NUM
位,每个位对应一个数的状态,因此每个bitset
占用的空间为MAX_NUM / 8
字节,即99999999 / 8 = 12.5MB
。
- 输入数组的空间:
test_numbers
是输入数组,它的空间复杂度是O(n)
,其中n
是数组的大小。
因此,空间复杂度主要由两个bitset
和输入数组决定,为O(MAX_NUM / 8 + n)
。
结论
- 时间复杂度:
O(n + MAX_NUM)
,其中n
是输入数组大小,MAX_NUM
是最大数的范围(99999999)。 - 空间复杂度:
O(MAX_NUM / 8 + n)
,主要由两个bitset
(每个占用约12.5MB)和输入数组决定。