1.无序数组找中位数
- 思路一
把无序数组排好序,取出中间的元素
时间复杂度 采用普通的比较排序法 O(N*logN)
如果采用非比较的计数排序等方法, 时间复杂度 O(N), 空间复杂度也是O(N). - 思路二
(1)将前(n+1)/2个元素调整为一个小顶堆
(2)对后续的每一个元素,和堆顶比较,如果小于等于堆顶,丢弃之,取下一个元素。 如果大于堆顶,用该元素取代堆顶,调整堆,取下一元素。重复2.2步
(3)当遍历完所有元素之后,堆顶即是中位数。
注:如果数组元素的个数是奇数,取数组前(size+1)/2个元素建堆,如果是偶数则取前 size/2 个元素建堆。但如果是数据流,数据个数是动态变动的,则应采用小根堆+大根堆的办法,具体见本文第5点介绍。 - 思路三
找中位数也可以用快排分治的思想。具体如下:
(1)任意挑一个元素,以改元素为支点,划分集合为两部分,如果左侧集合长度恰为 (n-1)/2,那么支点恰为中位数。如果左侧长度<(n-1)/2, 那么中位点在右侧,反之,中位数在左侧。
(2)进入相应的一侧继续寻找中位点。
注:可参考快排思想实现Top K
拓展:查找N个元素中的第K个小的元素,假设内存受限,仅能容下K/4个元素
分趟查找:
- 第一趟,用堆方法查找最小的K/4个小的元素,同时记录剩下的N-K/4个元素到外部文件。
- 第二趟,用堆方法从第一趟筛选出的N-K/4个元素中查找K/4个小的元素,同时记录剩下的N-K/2个元素到外部文件。
- …
- 第四趟,用堆方法从第一趟筛选出的N-K/3个元素中查找K/4个小的元素,这是的第K/4小的元素即使所求。
2.将十进制数字转化为X 进制的字符串
//将十进制数转化为X进制字符串
string trans(int num, int base){
string str;
while(num > 0){
if(num % base < 10)
str += num % base + '0';
else
str += num % base - 10 + 'A';
num = num / base;
}
reverse(str.begin(),str.end());
return str;
}
3.找出字符串中第k次出现的字符
- 思路一
遍历这个字符串(缺点是要对这个字符串查找好多次) - 思路二
(1) 先创建一个数组然后这个数组你就放成256个元素。
(2) 把这个字符串转化成ASCLL码进行查找。(因为ASCLL码的范围比较小)
(3) 当我们发现字符串中出现a(a的ASCLL为97)的时候,我们可以把刚才256个元素中下边为97的元素的值加一。
char find_first_K(string str, int K)
{
int count[256] = { 0 };
for (char ch : str) ++count[ch];
for (char ch : str) {
if (count[ch] == K)
return ch;
}
return '0';
}
5.找出数据流中的中位数
注意:始终保证小根堆A中元素个数不少于大根堆B中的元素个数,即A、B元素相等时,将B中最大元素加入A,否则将A中最小元素加入B,来维持元素个数平衡。
class MedianFinder {
private:
priority_queue<int,vector<int>, greater<int>> A; //小根堆
priority_queue<int,vector<int>, less<int>> B; //大根堆
public:
//插入新元素
void addNum(int num) {
if(A.size()==B.size()){ //把B中最大元素加入到A
B.push(num);
A.push(B.top());
B.pop();
}else{ //把A中最小元素加入到B
A.push(num);
B.push(A.top());
A.pop();
}
}
//查找当前中位数
int findMedian() {
return A.size()==B.size()?(A.top()+B.top())/2.0:A.top();
}
};
6.英文字符串流和中文字符串流如何分词
一种对英文字符串进行分词的方法:https://d.wanfangdata.com.cn/periodical/jsjyyyj200707016
字典与统计相结合的中文分词方法:https://d.wanfangdata.com.cn/periodical/xxwxjsjxt200609039
暴力方法:
- 英文分词:根据字符串中的空格、标点符号进行分词。
- 中文分词:固定两个字为一词进行拆分。
7.10亿QQ号去重
1. 内存够的情况
分段、map、多线程。
- 分段:哈希分桶,根据哈希值对桶数目取模得到对应桶号。
- map:需要计数采用unordered_map去重;不需要计数采用set去重。
- 多线程:将数据进行哈希分桶之后,各桶内map的去重可以采用多线程执行。
2. 内存不够的情况
思路一:bitmap
位图bitmap:每个int数字只用一个比特位来做标记
位图的操作(算法)基本依赖于下面3个元操作:
set_bit(char x, int n); //将x的第n位置1,可以通过x |= (1 << n)来实现
clr_bit(char x, int n); //将x的第n位清0,可以通过x &= ~(1 << n)来实现
get_bit(char x, int n); //取出x的第n位的值,可以通过(x >> n) & 1来实现
比如,要对数字int x = 1848105做标记,就可以调用set_bit(bit_map[x/8], x%8);
除法看做求“组编号”,x/8即是 以8个位为一个小组,分组到编号为idx = x/8
的bit_map元素中,然后在组内偏移lft = x%8
个比特位。
10亿数字(int 32位):10^8 * 32 / 8 = 40亿字节 / 1024 ≈ 400万 KB / 1024 ≈ 4000 MB / 1024 ≈ 4 GB
int 32位所需bitmap大小:2^32 / 8 = 2^29 字节 / 1024 = 2^19 KB / 1024 = 2^9 MB = 512 MB
10亿数字(long long 64位):4 GB * 2 = 8GB
long long 64位所需bitmap大小:2^64 / 2^23 = 2^41 MB / 1024 = 2^31 GB / 1024 = 2^21 TB / 1024 = 2048 PB = 2 EB
思路二:多路归并排序
问题:如何给100亿个数字排序?
注:100亿个 int 型数字放在文件里面大概有 37.2GB
- 把这个37GB的大文件,用哈希分成1000个小文件,每个小文件平均38MB左右(理想情况),把100亿个数字对1000取模,模出来的结果在0到999之间,每个结果对应一个文件,所以我这里取的哈希函数是 h = x % 1000,哈希函数取得”好”,能使冲突减小,结果分布均匀。
- 按各输入文件中下一个读到的元素的大小构造一个输入流最小堆.
- 从堆顶文件里读一个元素并写入输出文件.
- 同时按读的那个文件的下一个元素的值调整堆.
- 若第3步已到达文件结尾.则从堆中删除该输入流.
- 如果堆中还有元素. 回到第2步.
3. 外存不够的情况
考虑是不是可以进行分布式处理
待查~