公司分档论级
我认为研发技术的好公司的标准是,知名度高,对技术提升有很大帮助,将来换工作好跳槽,当然薪水至少中上。
据此:
一档公司:
Facebook
LinkedIn
Amazon
Google
准一档公司:
微软亚洲研究院MSRA
百度
阿里
腾讯
二档公司:
360
小米
猎豹移动
京东
搜狗
网易
乐视
华为
三档公司:
新浪
搜狐
以及其他互联网上市公司
其他烧钱的互联网公司除非钱给够,否则没意义。适合那种在前3档公司积累足够技术,去小公司带队伍的兄弟。
分档完毕。
内推资源
1年之内没投过的都可以(Fulltime Only),请在www.google.com/about/careers/search选择1-3个职位(New Grad可以不用选)。选好之后请将:
1.简历,
2.所选职位,
3.一小段个人简介(最好是英文的)
4.面试准备情况(大家也都
知道面挂了的话Google有一年的冷冻期,希望不要毫无准备就投,至少刷过1-2遍
leetcode),不要抱着随便试一试的心态,浪费机会。
发送至goog.refer@outlook.com
面试准备
- cc150,全名cracking the coding interview - 150 Programming Questions and Solutions。经典中的经典。优势在于分章节,每章突出一块知识,题目精炼,答案好找。
- leetcode。程序员刷面试题的第一网站,题多且全,答案非常好找。online judge能深度检验代码的正确性,刷leetcode是最能锻炼算法题能力的。
- 编程之美、剑指offer:大部分题目还是很优秀很巧妙的。重点是交叉对比。
- “结构之法”博客:只讲里面的算法题:“微软面试100题”(快500题)系列,堪称算法题的大宝库,而且很多题目很新,是面试官喜欢出的类型……不过这个系列的排版略微混乱,很多题也没有答案;“程序员编程艺术”系列,讲的很细致,适合深入去学习一些算法;经典库函数。这块单独拉出来,是因为考的很多,比如atoi,strstr,memcpy等等……在“程序员编程艺术”中,杂七杂八有相关的论述,最好自己系统整理一下。“教你如何迅速秒杀掉:99%的海量数据处理面试题”,很实用的海量数据处理面试文章。
- Geeksforgeeks. 讲解非常清楚明白易懂, 尤其在学习数据结构和算法上面帮助很大。
- Data structure & Algorithms. 最基础也是最重要的部分。每个数据结构一定要做到彻底明白概念, 结构, 功能, 怎么用. 一定要亲自在IDE里面至少implement一遍!! Data Structures and Algorithms in JAVA. 非常适合初学者, 每个数据结构的实现和用法都写的极其详细
- 非算法类书籍,扩宽开发知识面的:W.Richard Stevens著的《TCP/IP详解三卷》,《UNIX网络编程二卷》,《UNIX环境高级编程:第2版》
- 数学基础,包括微积分、线性代数、概率论与数理统计 (推荐一本《数理统计学简史》) 、矩阵论 (推荐《矩阵分析与应用》),这些对于机器学习,以及最近大火的人工智能都是有帮助的!
注意第4点提到的博客已经整理出书:
相关网站见:
http://taop.marchtea.com/
http://taop.marchtea.com/00.01.html
总而言之,光在IDE上敲是不够的,还要练习多在纸上写。若实在觉得算法 & 编程太难,转产品、运营、测试、运维、前端、设计都是不错的选择,因为虽然编程有趣,但不一定人人适合编程。
实际工作中,重要的还是项目能力。但是面试糙快猛走算法路线,那么需要搞定优先级:
1.面经提到的题目
2.经典题目:比如:atoi,LCS最长公共子序列问题,最长回文子序列LPS(Longest Palindromic Subsequence),单链表逆置。经典的题目毕竟出的最多,一定要非常熟练。
3.50行之内的新题,面试官通常时间有限,没时间让你写个上百行。
其他零碎准备
java
对于java,可以到这里刷面试题
http://careeronlinejobs.com/?atgid=421634339&cid=9CUH1E86G&clxd=7530339729507209378&folio=9POG2QVTL>k=&mchkwd=basic%20interview%20questions&tp1=7530339652607014974&tp2=7530339656907013698%7CF8E94792-9455-4FAD-963D-A3E2C243994D%7CB8A4FE69-61F4-49EA-B85B-1C32F6070BF7&tp3=7530339729507209378
Thinking in Java和Effective Java这两本书是必读的。
Big Data. 几乎所有大公司的面试都不约而同的添加了大数据相关的问题。看这个: http://blog.csdn.net/v_july_v/article/details/7382693。总结几乎囊括了所有大数据方面的知识背景。对于里面提到的不同方法要多比较, 每种方法什么时候适用, trade off是什么都要清楚. 重中之重是Map Reduce和External sort.
Thread & Locks. 考得不多。 主要知识点: thread和process区别, multithread, lock, semaphore, 对resource分配, deadlock, 怎么解决/预防deadlock. 还有BlockingQueue 和 Producer-Consumer经典题要会implement.
这里有几个经典问题:
http://www.careercup.com/question?id=4783236498587648
http://www.careercup.com/question?id=5652784707796992
OOD. 老老实实实现了两遍Singleton, Factory, 还有MVC pattern. 设计一个class应该也算在OOD范围里: 写过无数遍LRU, Trie, Iterator, BST以及变种, BlockingQueue等等
System Design. Facebook几乎肯定是要考系统设计的,还是得好好准备。一定要看FB的engineering blog。基础的概念至少要会: load balancer, cache, memcache, consistent hashing, round robin, master slave, sharding, pre-computed, map reduce, difference with SQL/NoSQL…. 有很多牛人总结的系统设计帖:
http://massivetechinterview.blogspot.com/2015/06/itint5.html
http://www.mitbbs.com/article_t/JobHunting/32777529.html
http://blog.csdn.net/sigh1988/article/details/9790337
https://www.udacity.com/course/viewer#!/c-cs253/l-48737165
简历
把自己简历上每个项目都弄熟,张口就来;
也要把目标公司的简介总结背诵。
常见问题要有腹稿:
为什么想加入?
为什么跳槽?
为什么适合这个职位。
一个面试官想要找:
不仅仅是能够做出题的人
一个合适的teammate(好说话,能聆听,马上接纳别人的idea并且有接受新知识的能力)
把想题的过程全部说出来, 不能成为心理活动, 让对方知道你在非常努力的思考, 而且态度很好, 所以就算你没有完全想出来, 他是非常愿意给你提示的。
应聘面经
第一轮,Graph题,给的是一个有方向的Graph,每个node都有颜色,有黑绿红三种颜色,制定的patten是以任意颜色的node开始,接着是大于等于一个绿色的node,以红色的node结尾,找出所有符合这种pattern的path的head node。
面试官画出来的是一个有序的Graph,所以是在有序Graph基础上加上颜色的属性。因为我对于Graph的掌握不是特别好,面试官讲说我可以从Tree这个角度下手,所以我先用Tree试着去做。
我的理解这个题目应该采用DFS,根据在dfs中传入color,去check当前的path是否符合pattern。写的过程中不是特别顺利,不过一直在跟面试官沟通,最终写了一版出来。面试官针对一些case提出疑问,然后就在code的基础上进行了一定的修改,最终的版本也没有去跑test case。不过时间也剩没几分钟,就简单的聊了几句后第二个面试官便来敲门了。
第二轮,判断这个数字是否可以用两个数字的平方加和而成。题目清晰之后,我跟面试官check了一下input的情况。便写出了一个简单的版本,时间复杂度为0(n) 。
public boolean isSumOfTwoSquares(int n){
for(int i = 0; i * i <= n; i++){
for(int j = 0; j * j <= n; j++){
if(i * i + j * j == n) return true;
}
}
return false;
}
因为input n是一个Integer,所以这个overflow的情况就是当i * i + j * j > n的情况,其实里边的循环改成j * j <= n - i会好一些。然后面试官的问题是当n = Integer.MAX_VALUE, I * i <= n is always true, so the loop will be like while true. 这个我跟面试官讲说,如果i * i 已经超出最大值的情况下,我不太清楚这个<=的结果是true还是false,(刚刚试了一下果然是true)。所以针对这个问题,我就将i * i <= n 改成了i <= Math.sqrt(n), j也是一样。这样的话有效的避免了这个问题。面试官问我接下来能不能优化,那我就从里层的循环下手,时间复杂度为o(sqrt(n))
public boolean isSumOfTwoSquares(int n){
for(int i = 0; i <= Math.sqrt(n); i++){
if(isSqrt(n - i * i)) return true;
}
return false;
}
public boolean isSqrt(int n){
int sqrt = (int)Math.sqrt(n);
if(sqrt * sqrt == n) return true;
return false;
}
面试官对于这个时间复杂度还比较满意,接下来的follow up就是面试官认为这个Math.sqrt方法不够efficient。问有没有可以不用这个方法的。可以用一次,然后将这个结果N储存下来作为一个变量。那这个其实就是可以用一个数据结构去存储所有的square value,然后将第二个方法改成check 数据结构中是否存在当前值。我当时不晓得脑子怎么秀逗了,竟然想用一个array,后来跟面试官聊了一会之后恍然大悟,为什么不用set,然后就用set写了一版。
public boolean isSumOfTwoSquares(int n){
int N = (int)Math.sqrt(n);
initialSet(N);
for(int i = 0; i <= N; i++){
if(isSqrt(n - i * i)) return true;
}
return false;
}
public boolean isSqrt(int n){
if(set.contains(n)) return true;
return false;
}
private Set<Integer> set = new HashSet<>();
public void initialSet(int N){
for(int i = 0; i <= N; i++){
set.add(i * i);
}
}
写到这里之后,针对于这个的时间复杂度,我们有进行了一点讨论,所以结果是依然是o(sqrt(n)),针对于这个set的contains的复杂度,因为有HashSet和TreeSet,我就讲到肯定HashSet,操作复杂度为o(1),又问了我下这俩set的区别,我就提到我基本上没有用过TreeSet,巴拉巴拉什么的。面试官开始跟我讨论test case。因为最开始出题的时候,我隐约听到了他提到n是正数,所以没有去check是否为负数。这时候加上了负数的check。还有跟面试官讨论了下0的return value的期望值,他认为是true。然后我就写了几个test case可以cover所有的corner case
isSumOfTwoSquares(-1);
isSumOfTwoSquares(0);
isSumOfTwoSquares(Integer.MAX_VALUE);
isSumOfTwoSquares(13);
isSumOfTwoSquares(3);
面试官对这个还比较满意。然后给我时间问问题,首先是问了些工作的问题,然后我就问道,在你的实际工作中,有什么应用到TreeSet的场景么,结果是no,从来没用过,哈哈。然后又聊了一会,第三个面试官迟到了,所以第二个面试官在回答问题的时候一直在瞅外边,好像着急要走的样子。
第三轮,华人大哥,全程黑脸, I don’t know why. 因为迟到了,所以上来没有握手寒暄,没有自我介绍,啪就把题甩出来了。
第一题input是两个数组,求数组A中存在但是B中不存在的,以及B中存在A中不存在的。
A [1,2,3,5]
B [2,2,4]
in A not in B [1,2,3,5]
in B not in A [2,4]
我的思路就是现将两个数组排序之后,从头进行比较,我用手动的方式展示了一下我的思路,得出来指定的结果,结果每次我给面试官投去眼光的时候,都发现他没有在看我。后来面试官讲到这其实把两个input调个个,就可以得出两个结果,一个helper方法就够了,然后让我在assume数组都已经排序的基础上进行处理。那我就开始code。
public List<Integer> helper(int[] arr1, int[] arr2){
List<Integer> res = new ArrayList<>();
int index = 0;
for(int i = 0; i < arr1.length; i++){
if(index >= arr2.length){
res.add(arr1);
}
if(arr1 < arr2[index]){
res.add(arr1);
}else{
index++;
}
}
return res;
}
写完之后,面试官看了一眼,没有问什么问题,就接着出题了。
第二题input是一个 integer数组, 求smallest subset sum bigger than target,target是全部数字求和的1%。
拿到题目之后,我当时首先想到的是难道可以用two sum或者什么的,结果发现不对付。那我首先思路是先排序,然后从大的开始找,当sum大于target的时候,这一定就是最短的subset。我用手写的方式展示了,第一个版本的代码是这样的。
public int minSubSet(int[] nums){
int sum = 0;
for(int num : nums){
sum += num;
}
int target = sum / 100;
int len = 0;
Arrays.sort(nums);
int length = nums.length - 1;
sum = 0;
for(int i = length - 1; i >= 0; i--){
sum += nums;
len++;
if(sum > target){
return len;
}
}
return len;
}
时间复杂度是o(nlogn),面试官提到这个OK,但是可以优化到o(n),当时确实一时没什么思路,思考了一会之后,还是向面试官求思路。给的思路是,从数组中随机取一个数字,然后将取出比这个数字大的subset,然后对这个subset求和与target比较,如果大的话就迭代操作。如果小的话,从小的数组里再取一部分。思考了一段时间,便开始编码。代码是这样的。
public List<Integer> minSubSet(int[] nums){
List<Integer> list = new ArrayList<>();
int sum = 0;
for(int num : nums){
list.add(num);
sum += num;
}
int target = sum / 100;
return helper(list, target);
}
public List<Integer> helper(List<Integer> list, int target){
int size = list.size();
Random random = new Random();
int n = list.get(random.nextInt(size));
List<Integer> biglist = new ArrayList<>();
List<Integer> smalllist = new ArrayList<>();
int sum = 0;
for(int num : list){
if(num > n){
biglist.add(num);
sum += num;
}else{
smalllist.add(num);
}
}
if(sum > target){
if(biglist.size() == 1) return biglist;
return helper(biglist, target);
}else{
List<Integer> tmp = helper(smalllist, target - sum);
for(int num : tmp){
biglist.add(num);
}
return biglist;
}
}
代码一开始不是bug free的,然后面试官也是给各种case,讨论执行过程。最后优化代码。然后到我问问题的时间,我就咨询了下加入谷歌后需要多少时间适应整个环境,实际上手这样。聊了一会下个面试官就来敲门了。
第四轮,面试小哥是做安卓的。上来先来了几个java 的基础问题,什么是abstract class,怎么用abstract class,interface与abstract class的区别。然后给了一个算是设计的题目。要求是给一个apply方法,有两个参数一个是list,另外一个算是一个function,要求不管传入什么样的操作都可以实现,比如list, funtion is times two,那返回的结果就是所有list的元素加倍。这个是最简单的例子,还有比如square或者pow各种各样的要求都要满足。一开始我的理解与题目有偏差,没有特别好的给出一个满意的解答。最后还是咨询了一下面试小哥,他的解答是用interface去实现。瞬间明了。一开始思考的方向就完全错了。接下来出题了。
第一题:给一个integer的数组,求数组中是否存在两个数字,一个是另一个的两倍。前边有用到set,所以此处我也采用set,跟小哥交流之后,讨论了可行性。然后开始编码。同时包括case 0的情况。
public boolean isDoubleExists(int[] nums){
Set<Integer> set = new HashSet<>();
for(int num : nums){
if(num == 0) return true;
if(set.contains(num * 2)) return true;
if(set.contains(num / 2) && num / 2 * 2 == num){
return true;
}
set.add(num);
}
Iterator<Integer> iter = set.iterator();
while(iter.hasNext()){
Integer num = iter.next();
if(set.contains(num * 2)) return true;
if(set.contains(num / 2) && num / 2 * 2 == num) return true;
}
return false;
}
Follow up是两倍的chain比如1 2 4,找最长的chain。这个题最开始的话我建议先排序,这样从小到大,会比较容易一些(现在一想,可能不排序分两倍和二分之一一样可以实现)。然后同样还是用set去做,但是是dfs的思路,不断更新max len的值。
int max = 0;
public int maxDoubleChain(int[] nums){
Arrays.sort(nums);
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
for(int num : set){
dfs(set, num, 1);
}
return max;
}
public void dfs(Set<Integer> set, int num, int len){
if(set.contains(num * 2)){
len++;
dfs(set, num * 2, len);
}else{
max = Math.max(max, len);
}
}
代码实现之后,小哥提问是否可以进一步优化,省去重复访问一些已经走过的值。我提到可以用map记录已经访问过的值,如果已经访问到的话,就直接return.
int max = 0;
public int maxDoubleChain(int[] nums){
Arrays.sort(nums);
Map<Integer, Boolean> map = new HashMap<>();
Set<Integer> set = new HashSet<>();
for(int num : nums){
set.add(num);
}
for(int num : set){
dfs(set, num, 1, map);
}
return max;
}
public void dfs(Set<Integer> set, int num, int len, Map<Integer, Boolean> map){
if(map.get(num)){
return;
}
map.put(num, true);
if(set.contains(num * 2)){
len++;
dfs(set, num * 2, len, map);
}else{
max = Math.max(max, len);
}
}
最后针对时间复杂度讨论了好久,最后我讲说因为sort是最大的所以是o(nlogn),小哥说,对,我就是在等这个。然后后来又聊了一会,最后一个面试小哥来敲门了。
第五轮,上来就寒暄一阵。小哥上来先针对我的简历问了几个问题,关于系统的维护已经如果有效的避免再次出现同样的问题。然后上题。题目是给一个String数组(我不晓得我怎么就跟数组杠上了),求最长的keyword。keyword的定义是,移除一个字符,剩下的字符串依然在数组中,一直移直到最后只剩下一个字符。拿到题目之后,思考一会之后沟通了下我的思路,因为要求最长,所以我建议先根据长度进行排序(又是排序)。还有一个提前break的情况是如果最短的字符串长度大于1,那没有keyword出现的可能性。这时候跟面试小哥沟通的结果是这个可以返回””.
public String findKeyword(String[] dics){
List<String> list = new ArrayList<>();
for(String word : dics){
list.add(word);
}
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if(o1.length() > o2.length()){
return -1;
}else if(o1.length() < o2.length()){
return 1;
}else{
return 0;
}
}
});
if(list.get(list.size() - 1).length() > 1) return "";
for(String word : list){
if(dfs(list, word)) return word;
}
return "";
}
public boolean dfs(List<String> list, String word){
if(word.length() == 1){
if(list.contains(word)) return true;
return false;
}
for(int i = 0; i < word.length(); i++){
String newword = word.substring(0, i) + word.substring(i + 1);
if(dfs(list, newword)){
return true;
}
}
return false;
}
第一次写出来的也不是bug free的,小哥也是给我提供各种case,然后不断改进代码,最后能满足需求。follow up就是如何去进行优化,避免重复check同样的字符串,比如有两个元素一个goat,一个boat,如何避免oat的重复check。那这里我就讲说用map去维护当前元素是否visited。
Map<String, Boolean> map = new HashMap<>();
public String findKeyword(String[] dics){
List<String> list = new ArrayList<>();
for(String word : dics){
list.add(word);
}
Collections.sort(list, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
if(o1.length() > o2.length()){
return -1;
}else if(o1.length() < o2.length()){
return 1;
}else{
return 0;
}
}
});
if(list.get(list.size() - 1).length() > 1) return "";
for(String word : list){
if(map.containsKey(word)){
if(map.get(word)) return word;
}else{
if(dfs(list, word)) return word;
}
}
return "";
}
public boolean dfs(List<String> list, String word){
if(word.length() == 1){
if(list.contains(word)){
map.put(word, true);
return true;
}
map.put(word, false);
return false;
}
for(int i = 0; i < word.length(); i++){
String newword = word.substring(0, i) + word.substring(i + 1);
boolean flag = dfs(list, newword);
if(flag){
map.put(newword, true);
return true;
}
map.put(newword, false);
}
return false;
}
解答完毕,然后可能有点超出时间,不过还是跟面试小哥愉快的交流了好半天,然后小哥送我出公司,开开心心的聊了一路。然后他就回去上班了。
其他面试题
1. 给一个undirected graph,举个栗子如下:
0-(0)--1--(1)--2
\ \
(1) (1)
\ \
3-(0)--4
给两个数字,代表起点和终点,请问最短的从起点到终点的路径,且最多经过一个(0)的arc
2. gray code原题不细说啦~https://leetcode.com/problems/gray-code/
follow up是返回n sequence的第m行.
3. C++语句:string a = b; 中,有没有string copy.
4. wiggle sort I 原题
5. We want to store True/False information in our quadtree. Examples:
A:
+-------+-------+ T: true
| | |F: false
| T | T |
| | |
+-------+-------+
| | |
| F | F |
| | |
+-------+-------+
A: B:
+-------+-------+ +-------+---+---+
| | | | |F | F |
| T | T | | T +---+---+
| | | | |T | T |
+-------+-------+ +-------+---+---+
| | | | | |
| F | F | | T | F |
| | | | | |
+-------+-------+ +-------+-------+
Child 1: T
Child 2:
Child 1: F
Child 2: F
Child 3: T
Child 4: T
Child 3: F
Child 4: T
B ch1 = 1, ch2 = 2(default), ch3=0,ch4=1 isleaf =F;
children[1]= null; children[2] C; children[3] =null; children[4]
Part 1: Write a data structure that can representa quadtree.
Part 2: Write code that will take two quadtrees and return aquadtree that represents the logical OR (or union) of the two trees.
A: B: C(A or B):
+-------+-------+ +-------+---+---+ +-------+-------+
| | | | |F | F | | | |
| T | T | | T +---+---+ | T | T |
| | | | |T | T | | | |
+-------+-------+ +-------+---+---+ +-------+-------+
| | | | | | | | |
| F | F | | T | F | | T | F |
| | | | | | | | |
+-------+-------+ +-------+-------+ +-------+-------+