1. 一千万个数据的去重问题
对数据内容求MD5值
MD5值的特点:
1.压缩性:任意长度的数据,算出的MD5值长度都是固定的。
2.容易计算:从原数据计算出MD5值很容易。
3.抗修改性:对原数据进行任何改动,哪怕只修改1个字节,所得到的MD5值都有很大区别。
4.强抗碰撞:已知原数据和其MD5值,想找到一个具有相同MD5值的数据(即伪造数据)是非常困难的。
根据MD5值的特点,对每条记录的维度数据内容计算MD5值,然后根据MD5值判断重复记录。
对数据入库之后利用sql直接查出重复数据,然后将重复数据移除或者标记。
至少在现阶段内存和CPU的执行效率在固定时间内是有限的,大量的数据的查重和去重处理不可能同时在内存中进行。就像外部排序算法和内部排序算法差别很大,遇到此类大量数据查重问题对算法进行设计是有必要的。
布隆过滤器
布隆过滤器是一种采用hash法进行查重的工具。它将每一条数据进行n次独立的hash处理,每次处理得到一个整数,总共得到n个整数。使用一个很长的数组表示不同的整数,每一次插入操作把这n个整数对应的位置的0设置为1(如果已经被设置为1则不变)。下次查找的时候经过同样的计算,如果这几个位置都是1则说明已经存在。
布隆过滤器的优点是使用方便,因为并不将key存放进内存所以十分节省空间,多个hash算法无关,可以并发执行效率高。缺点也是显而易见的,这种算法是可能出现错误,有误判率这种概念。通过hash的次数我们可以降低误判率,但是不能保证没有误判的情况。
BitMap
比如有2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
一个数字的状态只有三种,分别为不存在,只有一个,有重复。因此,我们只需要2bits就可以对一个数字的状态进行存储了,假设我们设定一个数字不存在为00,存在一次01,存在两次及其以上为11。那我们大概需要存储空间几十兆左右。接下来的任务就是遍历一次这2.5亿个数字,如果对应的状态位为00,则将其变为01;如果对应的状态位为01,则将其变为11;如果为11,,对应的转态位保持不变。
最后,我们将状态位为01的进行统计,就得到了不重复的数字个数,时间复杂度为O(n)。
hash分组
如果有两份50G的数据,要查重,内存4G,怎么查?
想法是先将50G的数据分别做hash%1000,分成1000个文件,理论上hash做得好那么这1000个文件的大小是差不多接近的。如果有重复,那么A和B的重复数据一定在相对同一个文件内,因为hash结果是一样的。将1000个文件分别加载进来,一一比对是否有hash重复。这种想法是先把所有数据按照相关性进行分组,相关的数据会处于同样或者接近的位置中,再将小文件进行对比。
2.int i=0;while(true)i++;一秒之后i的值
和计算机的cpu频率有关,例如2.3GHz的cpu,实际测试,i接近2.3亿
timeval start_time;
timeval end_time;
gettimeofday(&start_time,NULL);
int i = 0;
while(1) {
//Do something
i++;
gettimeofday(&end_time,NULL);
size_t a1 = ((start_time.tv_sec) * 1000 + start_time.tv_usec/1000.0);
size_t a2 = ((end_time.tv_sec) * 1000 + end_time.tv_usec/1000.0);
size_t diff = a2-a1;
if (diff > 1000) {
break;
}
}
cout << i << endl;
3. 1亿个ip地址,从大到小排序,思路
三种方法:
数据库排序(对数据库设备要求较高)
分治法(常见思路)
位图法(Bitmap)
方法概要
-数据库排序(对数据库设备要求较高)
操作:将数据全部导入数据库,建立索引,数据库对数据进行排序,提取出数据。
特点:操作简单, 运算速度较慢,对数据库设备要求较高。
分治法(常见思路)
操作:操作与归并排序的思想类似,都是分治。
将数据进行分块,然后对每个数据块进行内部的排序(假如是对int形数据升序)。
和归并排序类似,每个数据块取第一个数据(当前块的最小数据),然后比较取出的数据,取其最小加入结果集。
重复2操作,直到取完所有数据,此时排序完毕。
特点:
位图法(Bitmap)
操作:基本思想就是利用一位(bit)代表一个数字,例如第 3 位上为 1,则说明 3 这个数字出现过,若为0,则说明 3 这个数字没有出现过。很简单~
java.util 封装了 BitSet 这样一个类,是位图法的典型实现。
特点:
可读性差(不是一般的差 🤔)
位图存储的元素个数虽然比一般做法多,但是存储的元素大小受限于存储空间的大小。要想定义存储空间大小就需要实现知道存储的元素到底有多少
对于有符号类型的数据,需要用 2 位来表示,比如 第 0 位和第 1 位表示 0 这个数据,第 2 位和第 3 位表示 1 这个数据…,这会让位图能存储的元素个数,元素值大小上限减半
只知道元素是否出现,无法知道出现的具体次数
4.找出1到n中重复的数
-
1.hash表查次数 时空 O(n)
-
2.排序检查 时 O(nlogn), 空O(1)
-
3.有一个巧妙的办法是,我们遍历一遍数字,把nums[i]指向的数(即nums[nums[i]])做一个+n的操作,那么如果遇到一个nums[nums[i]]的值已经大于n了,说明这个数已经被其他数字指到过了,也就是找到了重复值。在执行的过程中,我们还要先判断一下nums[i]是否大于n(因为可能先前被别人指过所以+n了),用一个值来保存其原来的值。
- 二分搜索:
二分搜索法:二分搜索其实是个很神奇的东西,用的好可以举一反三用在各种地方,其实,关键就是你把left和right当做是哪里的值,一般人都只是把这两个当做是数组中的min和max(在本题1和n),或者是数组中的头尾(本题中的nums[0]和nums[n])。所以,适用不了。
其实,换个思路,我们只要把left和right当做是ans的下上界限就能别有洞天。接着开展二分搜索中的搜索过程:mid取中值,那么nums中的数就被分成了[left - mid - right]两端了。
然后我们遍历一遍nums,统计所有<=mid的值count,如果left + count > mid + 1,说明[ left - mid ]段的数字中存在重复(ans为其区间的值),所以令right = mid。
反之就是[ mid - right ]的数字,所以令left = mid + 1;
知道其结束条件即可。
时间复杂度O(nlogn),空间复杂度O(1),不用移动原数组,不用改动原数组。
int findDuplicate5(vector<int> nums) {
int n = nums.size();
int left = 1, right = n;
int mid, count;
while (left < right)
{
count = 0;
mid = (left + right) / 2;
for (int i = 0; i < n; ++i)
{
if (nums[i] >= left && nums[i] <= mid)
++count;
}
if (left + count > mid + 1)
right = mid;
else
left = mid + 1;
}
return left;
}
- 链表找环法
设置两个步长的指针:fast和slow,初始值都设置为头指针。其中fast每次2步,slow每次一步,发现fast和slow重合,确定了单向链表有环路。接下来,让fast回到链表的头部,重新走,每次步长1,那么当fast和slow再次相遇的时候,就是环路的入口了。
对于本题目:
其中,因为每个数字都会指向其他数字,或指向自己,所以可能会有多个环或一个环,并且因为有重复数字,所以它所占的位置会被别人多次指到,相当于多了圆环中的两个节点的部分,所以它会是有一个把手突出,并且因为有0这个位置 ,但是没有0这个元素,所以一定会有从0开始的环外p部分。
以上几点说明,在本题是一定会形成带把手的环的形状,并且环的起点就是重复的元素。
时间复杂度O(n),空间复杂度O(1),不用移动原数组,不用改动原数组。
代码如下:
int findDuplicate6(vector<int> nums) {
int slow = 0;
int fast = 0;
do{
slow = nums[slow];
fast = nums[nums[fast]];
} while (slow != fast);
fast = 0;
while (fast != slow){
fast = nums[fast];
slow = nums[slow];
}
return fast;
}
5.rand5实现rand7,rand7实现rand5
1.rand7实现rand5
int rand5() {
int x = rand7();
while (x >= 5) {
x = rand7();
}
return x;
}
2.rand5实现rand7
int rand7() {
int x = rand5() + rand5();
while (x >= 7) {
x = rand5() + rand5();
}
return x;
}
5.快速判断一个数是不是2的n次方
- 从1开始乘2与目标值判断,相等则目标值是,停止判断,大于则表示不是
public static boolean is2Power1(int num) {
int temp = 1;
while (temp <= num) {
if (temp == num) {
return true;
}
temp = temp << 1;
// temp = temp * 2;
}
return false;
}
2.目标值对2取余,如果余数不为0,则目标值不是,否则是
public static boolean is2Power2(int num) {
while (num > 1) {
if (num % 2 == 1) {
return false;
}
// num = num / 2;
num = num >> 1;
}
return true;
}
3.化成二机制表示
例如 8,二进制表示是1000, 7的二进制表示是0111,则1000&0111 = 0,所以
public static boolean is2Power3(int num) {
return (num & num - 1) == 0;
}
6.1G内存能存多少ip地址
1G = 1024 * 1024 * 1024个字节
一个ipv4占4字节
1024x1024x1024 / 4;
7.线程中断的方式
- interrupt方法
- 通过设置的标志位
- 通过抛出异常
- 通过线程间通信
中断的原理:
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。如果线程不响应中断,那么中断机制就对当前线程无效。
简单来说就是设置中断标示位
中断的响应
程序里发现中断后该怎么响应?这就得视实际情况而定了。需要由程序自己捕获异常处理或者通过调用 Thread.currentThread().interrupt() 来重新设置中断状态。然后交由上层继续处理。