模拟&&海量数据处理相关
BM97 旋转数组
import java.util.*;
public class Solution {
public int[] solve (int n, int k, int[] nums) {
k %= nums.length;
reverse(nums, 0, nums.length - 1);
reverse(nums, 0, k - 1);
reverse(nums, k, nums.length - 1);
return nums;
}
public void reverse(int[] nums, int i, int j) {
while (i < j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
i++;
j--;
}
}
}
BM98 螺旋矩阵
import java.util.ArrayList;
public class Solution {
ArrayList<Integer> list=new ArrayList<>();
public ArrayList<Integer> spiralOrder(int[][] matrix) {
if(matrix.length==0) return list;
int l=0,r=matrix[0].length-1,t=0,b=matrix.length-1;
while(true){
for(int i=l;i<=r;i++) list.add(matrix[t][i]);
if(++t>b) break;
for(int i=t;i<=b;i++) list.add(matrix[i][r]);
if(--r<l) break;
for(int i=r;i>=l;i--) list.add(matrix[b][i]);
if(--b<t) break;
for(int i=b;i>=t;i--) list.add(matrix[i][l]);
if(++l>r) break;
}
return list;
}
}
BM99 顺时针旋转矩阵
import java.util.*;
public class Solution {
public int[][] rotateMatrix(int[][] mat, int n) {
for(int i=0;i<n/2;i++){
for(int j=0;j<n;j++){
int tmp=mat[i][j];
mat[i][j]=mat[n-1-i][j];
mat[n-i-1][j]=tmp;
}
}
for(int i=0;i<n;i++){
for(int j=0;j<i;j++){
int tmp=mat[i][j];
mat[i][j]=mat[j][i];
mat[j][i]=tmp;
}
}
return mat;
}
}
BM100 设计LRU缓存结构
import java.util.*;
public class Solution {
class Node {
public int key, val;
public Node next, pre;
public Node(int k, int v) {
this.key = k;
this.val = v;
}
}
class DoubleList {
private Node head, tail;
private int size;
public void addFirst(Node node) {
if (head == null) head = tail = node;
else {
Node n = head;
n.pre = node;
node.next = n;
head = node;
}
size++;
}
public void remove(Node node) {
if (head == node && tail == node) {
head = null;
tail = null;
} else if (tail == node) {
node.pre.next = null;
tail = node.pre;
} else if (head == node) {
node.next.pre = null;
head = node.next;
} else {
node.pre.next = node.next;
node.next.pre = node.pre;
}
size--;
}
public Node removeLast() {
Node node = tail;
remove(tail);
return node;
}
public int size() {
return size;
}
}
private HashMap<Integer, Node> map;
private DoubleList cache;
private int cap;
public Solution(int capacity) {
this.cap = capacity;
map = new HashMap<>();
cache = new DoubleList();
}
public int get(int key) {
if(!map.containsKey(key)) return -1;
int val = map.get(key).val;
set(key, val);
return val;
}
public void set(int key, int value) {
Node x = new Node(key, value);
if (map.containsKey(key)){
cache.remove(map.get(key));
cache.addFirst(x);
map.put(key,x);
} else {
if (cap == cache.size()) {
Node last = cache.removeLast();
map.remove(last.key);
}
cache.addFirst(x);
map.put(key,x);
}
}
}
海量数据处理相关
BitMap
BitMap可用于海量数据去重和排序,节约空间。申请一个byte数组,数组中的每一个bit映射到一个数上,bit值代表对应的数是否出现过(0表示未出现,1表示出现过)。
使用举例
-
问:5TB的硬盘上放满了数据,请写一个算法将这些数据进行排重。如果这些数据是一些32bit大小的数据该如何解决?如果是64bit的呢?
-
答:假设所有数据均为int32,如果使用
HashSet<Integer>
来实现去重,则需要一个大小最大为232的哈希表;如果将int32中的每一个数字用1bit来标志是否存在,则一共需要232bit=229Byte=29MB=512MB空间。
对于小数据量、数据取值很稀疏,上面的方法并没有什么优势,但对于海量的、取值分布很均匀的集合进行去重,Bitmap极大地压缩了所需要的内存空间。于此同时,还额外地完成了对原始数组的排序工作。缺点是,Bitmap对于每个元素只能记录1bit信息,如果还想完成额外的功能,恐怕只能靠牺牲更多的空间、时间来完成了。
-
已知某个文件内包含一些电话号码,每个号码为8位数字,统计不同号码的个数。
8位最多99 999 999,大概需要99M个bit,大概10几M字节的内存即可。
-
2.5亿个整数中找出不重复的整数的个数,内存空间不足以容纳这2.5亿个整数。
将bit-map扩展一下,用2bit表示一个数即可,00表示未出现,01表示出现一次,10表示出现2次及以上。或者我们不用2bit来进行表示,我们用两个bit-map即可模拟实现这个2bit-map。
-
给定100亿个整数,设计算法找到只出现1次的整数。可用内存为1GB。
题目的要求是找出只出现一次的数字,所以我们需要2个位来表示一个数的状态。00不存在,01出现1次,10出现多次,11无意义。
**Tips:1GB内存=80亿bit位,512M内存=40亿bit位,int最大可以表示40亿。
所以这80多亿个位刚好可以表示所有的整数(int最大可以表示40亿)。
即第0和第1位表示数字1的状态,第2位和第3位表示数字2的状态,以此类推。
通过这个方法,我们可以快速筛选出只出现一次的整数。
-
40 亿无符号整数,内存 1G,怎么排序?
- 如果是无重复数字,则让每个数字对应一个bit,需要
4*(10^9)bit=0.5*(10^9)Byte=0.5GB
- 将每个数字映射到一个bit位置中
- 遍历bitmap,为1的位置则找出对应的下标并还原位数字写入文件即可
- 如果是无重复数字,则让每个数字对应一个bit,需要
Bloom Filter
布隆过滤器:使用bit数组来标识一个key是否(1/0)存在,原始key经过多个不同的hash函数得到对应的下标值。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZvqCPaPp-1651838654371)(%E6%A8%A1%E6%8B%9F.assets/image-20210723172351253.png)]
- 当需要判断某个元素是否存在时,只需要计算多个哈希值如果对应下标均为1则该元素可能存在,只要有0一定不存在。
- 布隆过滤器存在小的误判概率,理论上来说过滤器中存储的元素越多则误判概率越大!
布隆过滤器存在的缺点:
- 数据库中数据更新后不能及时体现到布隆过滤器中,比如某个商品被删掉了,但布隆过滤器中的某个位的值不能被删除因为这个位置上的值有可能其他商品在占用。
- 需要定时清除过滤器中的数据,定时加载。
使用举例
- 使用布隆过滤器解决缓存击穿问题:将数据库中的所有key加载到布隆过滤器中,当用户请求一个不存在的key时直接在布隆过滤器处即可快速拦截,如果布隆过滤器判断可能存在再继续到数据库中进行查询。
归并(分治)
针对大量数据无法直接全部载入内存时,可以采用分治的思想进行处理:
- 首先将大数据切割成几个小的块,对每个块进行单独排序(可以使用快排)后存入磁盘中;
- 最后不断从每个已排序好的块中取少量数据载入内存输入缓存区并进行多路归并,同时不断将排序结果从内存输出缓存区输出到磁盘即可;
- 当所有块中的数据都已经读取并归并完毕后,磁盘中存储的即为全部排序好的数据。
使用举例
1TB数据使用32GB内存如何排序?
- 把磁盘上的1TB数据分割为40块(chunks),每份25GB。(注意,要留一些系统空间!)
- 顺序将每份25GB数据读入内存,使用quick sort算法排序,把排序好的数据(也是25GB)存放回磁盘。
- 循环40次,现在,所有的40个块都已经各自排序了。(剩下的工作就是如何把它们合并排序!)
- 从40个块中分别读取25G/40=0.625G入内存(40 input buffers)。
- 执行40路合并,并将合并结果临时存储于2GB 基于内存的输出缓冲区中。当缓冲区写满2GB时,写入硬盘上最终文件,并清空输出缓冲区;当40个输入缓冲区中任何一个处理完毕时,写入该缓冲区所对应的块中的下一个0.625GB,直到全部处理完成。
两个大文件a和b中找出共同记录(如文件中存储一行行的URL)?
常规思路:
- 遍历文件a,将文件a中的记录加入哈希表中,依次取文件b中的记录到哈希表中查找即可。【文件太大,哈希表无法存储在内存中】
- 将文件a和b分别分成M块和N块,两两载入内存中利用哈希表做对比。【复杂度为O(MN),存在很多重复读取】
分治思路:
- 遍历文件a,对每个url求取hash(url)%N,然后根据所取得的值将url分别存储到N个小文件中(记为N0/1/2/3…)
- 遍历文件b,采取和a相同的方式将url分别存储到N个小文件(即为M0/1/2…)
- 最终比较a和b中的对应序号的文件对中是否有相同元素即可(利用常规的哈希表方法),因为相同的记录哈希值一定相同即一定会放到同一个标号的小文件中。
例题2:给一个100G大小的log file, log中存着IP地址, 设计算法找到出现次数最多的IP地址?
利用Hash切分,把100G切成100份,那么相同的IP地址必然在同一份里,之后对每一份(大小1G,可放内存里)利用哈希表或者Map来计数,最后统计。
例题3:给两个文件,分别有100亿个整数,我们只有1G内存,如何找到两个文件交集。
两个文件各自Hash取模切分成200份,每份500M,拿取模相同的文件分别做位图处理,用0表示数字存在,用1表示数字不存在,然后同时遍历两个位图,取都是1的,其对应的数就存在。
Tips**:**1GB内存=80亿bit位,int最大可以表示40亿。
例题4:给两个文件,分别有100亿个URL,我们只有1G内存,如何找到两个文件交集。
两个文件各自Hash取模切分成200份,每份500M,拿取模相同的文件,第一个做遍历并放入HashMap,另一个遍历去查HashMap,如果有值就是交集。
堆
针对海量数据求前K大/小的数据(K相对来说比较小),可以使用一个大小为K的小顶堆/大顶堆来完成。
扩展:双堆,一个最大堆与一个最小堆结合,可以用来维护中位数!
使用举例
100w个数中找最大的前100个数?
- 建立一个大小为100的小顶堆
- 将100w数据中的数字依次入堆(如果堆中元素个数大于100则弹出堆顶元素)
也可以采用归并的思路,当最后一步输出元素达到100个时停止多路归并即可。
放入HashMap,另一个遍历去查HashMap,如果有值就是交集。
堆
针对海量数据求前K大/小的数据(K相对来说比较小),可以使用一个大小为K的小顶堆/大顶堆来完成。
扩展:双堆,一个最大堆与一个最小堆结合,可以用来维护中位数!
使用举例
100w个数中找最大的前100个数?
- 建立一个大小为100的小顶堆
- 将100w数据中的数字依次入堆(如果堆中元素个数大于100则弹出堆顶元素)
也可以采用归并的思路,当最后一步输出元素达到100个时停止多路归并即可。