一.海量数据处理
1.给一个超过100G大小log file ,log中存着IP地址,设计算法找到出现次数最多的IP地址
- 100G的文件给我们的感觉就是太大,我们的电脑内存一般都为4G左右所以不能一次性把这么多的信息都加载到内存,所以就要进行切分成100份.IP地址是字符串比较长,我们可以把它转化为整形%100,这样取模后的值都落在0-99的区间里,所取模后值相同的IP地址都被分配到同一个文件,这时我们就可以采用哈希表统计出每个文件中最多的那个IP地址,最后比较得到100个IP中最大的那个IP就可以了
2.与上题条件相同,如何找到top k的IP?
- 看到求top 个IP就要立马反应到使用堆排序,这里的堆排序应该注意的是要建一个小堆,想一下我们建大堆的话只能保证堆顶元素为最大的,这样只能求出最大的那个IP
3.给定100亿个整数,设计算法找到只出现一次的整数
- 整数分为有符号和无符号两种,有符号数的取值为-21474836482147483648是从-21亿到+21亿,无符号数的范围为04294967296是从0到42亿,然而给了我们100亿个整数,要找到只出现一次的整数,所以我们还是要用到哈希表的思想,但我们最好不要定义一个整型数组,因为42亿*4B约为16G,这么大的数组我们再进行切分的话就太麻烦了,这里我们可以使用BitMap,用一个位来表示一个数存不存在,不存在表示为0,出现一次表示为1,出现一次以上用另一个位表示. 这样就可以将数组的大小减为原来的16分之1. 还遇到一个问题,就是到底怎么定义这个数组,正数好定义,负数的话我们可以用32位全1(-1)和它取异或或取到和正数相同的位置,我们此时定义一个二维数组,一半表示正数一半表示负数,都位于同一行,此时我们使用1G的空间就可以解决这个问题了
- 拓展:要是面试官问我们这里只有500M或者更少的空间的话怎么做?
- 同样采用切分的思想,不过我觉得这里我们直接可以按数的范围直接切分.要是有500M内存的话,我们就切一次就可以了,此时如果我们有50%的几率一次就找到这个只出现一次的数,效率可能更高
4.给两个文件,分别由100亿个query,我们只有1G内存,如何找到两个文件交集分别给出精确算法和近似算法
- 求两个文件的交集,这种算法我们肯定要用到比较.如果我们把两个文件都均分为100份,拿一个文件里的一份分别与另一个文件里的100份分别比较一次的话效率太低,我们可以借用第一道题的思维对他进行取模,这样我们只要比较取模的为同一值的两个文件比较就可以了,如果相同则标记
5.如何扩展BloomFilter使得它支持删除元素的操作?
- BloomFilter(布隆过滤器是一个bit向量或者说bit数组,可自行查看相关布隆过滤器的介绍)并不支持删除元素的操作,因为很可能产生哈希冲突(就是由不同的哈希函数算出的位置指向同一个位),这样改变一个位和可能会影响到其他元素的判断.这里我们可以按照和智能指针sharedptr的思想即"引用计数"来解决,我们添加一个cSount计数器,每当我们在这个位上表示一个元素时,就让他count++,每删除一个涉及到这个位表示的元素时就让它count–,这样只有当count为0时我们再对这一位置0,这样就完成了删除的操作
6.给上千个文件,每个文件大小为1K-100M,给n个词,设计算法对每个词找到包含他的文件,只有100K内存
- 我们可以使用布隆过滤器来判断一个文件是否包含这n个单词生成n个布隆过滤器放到外存,我们事先定义好一个包含这n个单词信息的文件info,每当我们在一个文件找到一个对应的单词就将这个文件的信息写入info对应单词的位置.我们只有100k内存,这100k内存我们一部分用来存放布隆过滤器一部分可以存放文件,因为文件最小都为100k,所以我们可以尝试把它切分为50k的小文件,每个文件标志好所属的大文件,这样我们每次读入一个布隆过滤器和一个小文件,如果这个文件有对应的单词则在info中标记所属大文件的信息,如果没有则读入下一个布隆过滤器,把所有布隆过滤器都使用后,再读下一个文件重复上述步骤直至把所有文件都遍历完
7.有一个词典,包含N个英文单词,现在任意给一个字符串,设计算法找出包含这个字符串的所有英文单词
- 首先判断一个单词是否包含一个字符串我们可以用instr这个函数,对于这个问题,我觉得如果该字符串的前缀和要找的单词一样的话可以采用字典树来查找,但是N个英文单词我们可以假设它很大,我们打它放到一个文件里,每次只读出固定个数单词进行判断.
- 总结:对于此类大数据问题我们一般都是采用哈希切分即模上一个数组的长度将数据分配到一个合理的位置,同时将一个大文件切分为小文件,这样特别方便将其与其他数进行比较例如对IP地址取整后进行哈希切分,或者对内部元素进行操作.使用BloomFilter可以进行判断元素在集合的存在与否
二.数据结构
1.冒泡排序
- 算法思想:
- 1.将序列中所有元素两两比较,将最大的放在最后面
- 2.将剩余序列中所有元素两两比较,将最大的放在最后面
- 3.重复第二步,直到只剩下一个数
/**
* 冒泡排序:两两比较,大者交换位置,则每一轮循环结束后最大的数就会移动到最后
* 时间复杂度为O(n^2) 空间复杂度为O(1)
* @param arr
*/
private static void bubbleSort(int[] arr){
//外层循环length-1次
for (int i = 0; i < arr.length-1; i++) {
//外层每循环一次最后都会排好一个数
//所以内层循环length-1-i次
for (int j = 0; j < arr.length-1-i; j++) {
if(arr[j]>arr[j+1]){
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
2.二分查找
- 1.算法概念
- 二分查找算法也称为折半搜索,二分搜索,是一种在有序数组中查找某一特定元素的搜索算法.请注意这种算法是建立在有序数组基础上的
- 2.算法思想
- 1)搜索过程从数组的中间元素开始,如果中间元素正好是要查找的元素,则搜索过程结束
- 2)如果某一特定元素大于或者小于中间元素,则在数组大于或与小于中间元素的那一半中查找,而且跟开始一样从中间元素开始比较
- 3)如果在某一步骤数组为空,则代表找不到
- 4)这种搜索算法每一次比较都使搜索范围缩小一半
//时间复杂度为O(log n)
public static int binarySearch(int[] srcArray,int des){
int low = 0;
int height = srcArray.length-1;
while (low<=height){
int middle = (low+height)/2;
if(des==srcArray[middle]){
return middle;
}else if(des < srcArray[middle]){
height = middle-1;
}else {
low = middle+1;
}
}
return -1;
}
3.递归方法实现二分查找
public static int binarySearch(int[] dataset,int data,int beginIndex,int endIndex){
int midIndex=(beginIndex+endIndex)/2;
if(data<dataset[beginIndex] || data>dataset[endIndex] || beginIndex>endIndex){
return -1;
}
if (data<dataset[midIndex]){
return binarySearch(dataset,data,beginIndex,midIndex-1);
}else if(data>dataset[midIndex]){
return binarySearch(dataset,data,midIndex+1,endIndex);
}else {
return midIndex;
}
}
4.单链表反转
class ListNode{
int val;
ListNode next;
ListNode(int x){
val = x;
}
}
public static ListNode reverseList(ListNode head){
ListNode prev = null;
while(head != null){
ListNode temp = head.next;
head.next=prev;
prev=head;
head=temp;
}
return prev;
}
5.插入排序
- 1.初始时假设第一个记录自成一个有序序列,其余记录为无序记录
- 2.接着从第二个记录开始,按照记录的大小依次将当前处理的记录插入到其之前的有序序列中
- 3.直至最后一个记录插入到有序序列中为止
public static void insertSort(int[] a){
int temp;
for(int i=1;i<a.length;i++){
for(int j=i;j>0;j--){
if(a[j-1]>a[j]){
temp=a[j-1];
a[j-1]=a[j];
a[j]=temp;
}
}
}
}
6.选择排序
- 把最小或者最大的选择出来
- 1)对于给定的一组记录,经过第一轮比较后得到最小的记录,然后将该记录与第一个记录的位置进行交换
- 2)接着对不包括第一个记录以外的其他记录进行第二轮比较,得到最小的记录并与第二个记录进行位置交换
- 3)重复该过程,直到进行比较的记录只有一个时为止
public static void selectSort(int[] a){
if (a==null || a.length<=0){
return;
}
for (int i = 0; i < a.length; i++) {
int min = i;
for (int j = i+1; j <a.length ; j++) {
if(a[j]<a[min]){
min=j;
}
}
if(i!=min){
int temp = a[min];
a[min] = a[i];
a[i] = temp;
}
}
}
7.队列:一种简单的数据结构,采用的调度策略是先进先出
8.二叉树的概念和特点
-
1.二叉树概念
-
二叉树是一种非常重要的数据结构,他同时具有数组和链表各自的特点,它可以像数组一样快速查找,也可以像链表一样快速添加.但是他也有自己的缺点:删除操作复杂.所谓二叉树的层数,就是深度.具体二叉树分类如下:
-
二叉树:是每个节点最多有两个子树的有序树,在使用二叉树的时候,数据并不是随便插入到节点中的,一个节点的左子节点的关键值必须小于此节点,右子节点的关键值必须大于或者等于此节点,所以又称二叉查找树,二叉排序树,二叉搜索树
-
完全二叉树:若假设二叉树的高度为h,除第h层外,其他各层(1~h-1)的结点数都达到最大个数,第h层有叶子结点,并且叶子结点都是从左到右依次排布,这就是完全二叉树
满二叉树:除了叶子结点外每一个节点都有左右子叶且叶子结点都处在最底层的二叉树 -
2.二叉树的特点
-
1)树执行查找,删除,插入的时间复杂度都是O(logN)
-
2)遍历二叉树的方法包括前序,中序和后序
-
3)非平衡树指的是根的左右两边的子节点的数量不一致
-
4)在非空二叉树中,第i层的结点总数不超过2^(i-1),i>=1
-
5)深度为h的二叉树最多有2^(h-1)个结点(h>=1),最少有h个结点
-
6)对于任意一颗二叉树,如果其叶节点树为N0,而度为2的结点总数为N2,则N0=N2+1
9.给你一个数组里面有奇数,偶数,写一个算法实现奇数全在最左侧,偶数全在最右侧
/**
* 定义一个方法接受一个int数组,在方法内新建一个数组
* 将传进来的数组中的元素装进去,但是要求奇数在左,偶数在右
* 最后返回这个新数组,在main方法中调用定义数组,调用该方法,获取返回值
* 遍历输出返回的数组
*/
public class Test{
public static int[] newArray(int[] arr){
int[] newArr = new int[arr.length];//定义新数组
//定义两个变量
int index1=0;
int index2=arr.length-1;
for (int i = 0; i < arr.length; i++){
if(arr[i]%2!=0){
//奇数放到新数组左边
newArr[index1]=arr[i];
index1++;
}else {
//偶数放到新数组右边
newArr[index2]=arr[i];
index2--;
}
}
return newArr;
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7,8,9,0};
int[] newArr = newArray(arr);
//遍历输出
for (int i = 0; i < newArr.length; i++) {
System.out.println(newArr[i]+"\t");
}
}
}
10.java数据结构
Java主要有以下一些数据结构
- 1)数组
- 2)链表
- 3)栈和队列
- 4)二叉树
- 5)堆和堆栈
- 6)散列表
- 7)红黑树