投递简历的过程:
7.17 号在上班的过程中突然实习僧的 hr 打电话给我说看到我的简历和阿里巴巴的游戏开发岗位比较符合,希望我去网申投递简历,OS(虽然知道自己大概率会凉,但是肯定要投啊),然后问了我一些项目开发的事情,和面试时间上的沟通。这说明我们在招聘网上注册的信息,公司是可以通过查看简历来了解是否和岗位相符合来找人投递简历。我问了当时去面试的人,他们都是这样子的。
7.20号 面试
了解到的基本信息:
- 总共三轮面试:两轮技术面,一轮 hr 面试
- 阿里巴巴是按照研究生的标准招人的
- 如果过了一轮面试,会有下一轮面试,如果没过会让你先回家
技术面试的问题:
1. 100 亿个不重复的 int 类型数据放到内存中,需要占用多少内存?
答: int是四个字节,所以是100亿*4 byte。
2. 如果 100 亿个不重复的数据上限下限分别是 正负100亿,那么要用什么类型存放。
答:
可以看到 int 32位存放的最大数据大概是 21亿,而 long远远大于 100亿。记住大概的值非常有用,因为在面试的过程中如果不知道大概的值,不好判断是选取哪个类型。
3. 判断单链表是否存在环
方法一:穷举遍历:
首先遍历指针 q 从头节点开始,依次遍历单链表的每一个节点且用一个计数器存放遍历节点走的步数。每遍历到一个新节点,检测节点 k 从头节点重新遍历新节点之前的所有节点,检测遍历节点和检测节点指向同一个节点。若指向同一个节点,则说明该节点被遍历过两次,链表有环;如果之前的所有节点当中不存在相同的节点,就继续遍历下一个新节点,继续重复刚才的操作,直到遍历节点指向 NULL 节点为止。
例如这样的链表:A->B->C->D->B->C->D, 当遍历到节点D的时候,我们需要比较的是之前的节点A、B、C,不存在相同节点。这时候要遍历的下一个新节点是B,B之前的节点A、B、C、D中恰好也存在B,因此B出现了两次,判断出链表有环。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL)
return false;
ListNode *k=head;//检测节点
ListNode *q=head->next;//遍历节点
int count=0;//记录检测节点走了多少步
while(q)
{
for(int i=count;i>0;i--)
{
k=k->next;
if(k==q)
return true;
}
k=head;//还原
q=q->next;
count++;
}
return false;
}
};
复杂度分析: 检测节点 k 移动的次数远远大于遍历节点,当链表不存在环时,检测节点的移动次数为 1+2+3+。。。+n ,计算可以直到时间复杂度为 O ( n 2 ) O(n^2) O(n2)。空间复杂度为 O ( 1 ) O(1) O(1)。
方法2:标记
在链表中增加一个域来标识当前链表是否被遍历,LinkList.visit=true 时说明该节点被遍历过,若遇到了遍历过的节点说明链表有环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* bool visit;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode *q=head;
while(q)
{
if(q->visit==true)
return true;
else
q->visit=true;
}
return false;
}
};
复杂度分析: 若存在环,只需要遍历到第 n+1 次,故时间复杂度为 O ( n ) O(n) O(n) , 空间复杂度是 O ( n ) O(n) O(n)。
方法3:快慢指针
设置一个 fast 指针一次走两步,设置一个 slow 指针,一次走一步,如果链表存在环,则 fast 和 slow 会相遇。
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL||head->next==NULL)
return false;
ListNode *fast=head->next->next;
ListNode *slow=head->next;
while(fast)
{
slow=slow->next;
for(int i=0;i<2;i++)
{
if(fast->next==NULL)
return false;
fast=fast->next;
if(fast==slow)
return true;
}
}
return false;
}
};
复杂度分析: 假设从链表头节点到入环点的距离是 D,链表的环长是 S。那么循环会进行 S 次(为什么是S次,有心的同学可以自己揣摩下),可以简单理解为 O ( n ) O(n) O(n)。除了两个指针以外,没有使用任何额外存储空间,所以空间复杂度是O(1)。
方法4:set集合大小变化
将节点放入 set 集合中,若存在相同的节点,那么插入时 set 中的大小不会变化。当 set 中的大小没有变化说明链表有环
class Solution {
public:
bool hasCycle(ListNode *head) {
set<ListNode*> a;
ListNode *q=head;
int count=0;//记录set的大小
while(q)
{
a.insert(q);
if(count==a.size())
return true;
q=q->next;
count=a.size();
}
return false;
}
};
复杂度分析:
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
n
)
O(n)
O(n)
4. 有一个游戏如何设计一个接口,统计近五分钟内用户的登陆次数?
我的回答:
1.维护一个小根堆,小根堆的根节点是当前的时间的前五分钟,若现在时间是21.40,则根节点是21.35,若用户登陆则插入到小根堆中,随着时间的推移,维护小根堆,若根节点不是近五分钟内则删除。最后堆的大小就是用户近五分钟的登陆次数。但是近五分钟内用维护堆的办法维护成本太高大题小作。
2 用一个大小为300的数组,若用户登陆就插入数组,并记录数组的大小,并动态检测数组中的数据是否在近五分钟内,若不在近五分钟内则删除。
5. 如何在 n 个数中找出前 k 个最大的元素?
答:维护一个大小为 k 的小根堆,向堆中插入 k 个元素后,若当前数据大于根节点,则插入当前数据,删除根节点,在通过堆的上浮和下沉操作重新变成小根堆。
复杂度:堆排序重建堆时间复杂度为 O ( n l o g n ) O(nlogn) O(nlogn)
6.两个线程同时访问一个变量如何避免竞争?
答:当多个线程几乎同时修改某一个共享数据的时候,需要进行同步控制。否则会出现竞争,线程同步能够多个线程安全访问竞争资源,最简单的同步机制是引入互斥锁。互斥锁为资源引入一个状态:锁定/非锁定。某个线程要更改共享数据时,就先将其锁定,此时资源的状态为 “锁定”,其他线程不可以更改;知道线程释放该资源,将资源的状态改为 “非锁定”,其他的线程才可以再次锁定该资源。互斥锁保证了每次只有一个线程进行写入操作,从而保证了多线程下数据的正确性