场景题------

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了),用一个值来保存其原来的值。

  1. 二分搜索:
    二分搜索法:二分搜索其实是个很神奇的东西,用的好可以举一反三用在各种地方,其实,关键就是你把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;
}
  1. 链表找环法
    设置两个步长的指针: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. 从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.线程中断的方式

  1. interrupt方法
  2. 通过设置的标志位
  3. 通过抛出异常
  4. 通过线程间通信

中断的原理:
Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。如果线程不响应中断,那么中断机制就对当前线程无效。
简单来说就是设置中断标示位

中断的响应
程序里发现中断后该怎么响应?这就得视实际情况而定了。需要由程序自己捕获异常处理或者通过调用 Thread.currentThread().interrupt() 来重新设置中断状态。然后交由上层继续处理。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Michael.Scofield

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值