本文由 杨珏成 首发于 掘金,未经许可请勿转载 原文链接:https://juejin.im/post/5d70ff205188253e4b2f07bd
我就读于北京理工大学软件工程专业,是一名大四学生。从大一开始投入以前端为主的全栈开发,独立开发过多个中型和小型项目,是 佬铁|宿舍市集 小程序的社区创始人及独立项目负责人。
在学校里读书的时候,我就是一个闲不住的人。最近因为一个偶然的契机,接触到了校招季,最后定下了本科毕业就工作的规划目标。
于是我在一个月的时间里参加了9家国内大厂校招,收获了如下结果(截至2019年9月5日):
从一开始手指冒汗被怼的说不出话,到最后和面试官侃侃而谈游刃有余,我发现:同样的能力水平,在不同的面试表现下,反馈到面试官眼中的结果可以有着天壤之别。
因此,如果你希望把自己的真实水平展示给面试官,那么掌握一些合适的方法是非常有必要的。
正文的内容聚焦于应聘大厂校招所需具备的能力,以及分析各个大厂具体的校招策略。分为两个部分:
希望能为第一次走上职场的同学们提供参考,也是对自己过去数周奔波忙碌的一个总结。
这里一定要在开头强调一下内推,引用一下知乎@Tony的回答:
内部推荐很可能会加速你的申请过程,但很小可能增加你被录取的成功率。 每天投简历到Google的人是很多很多的。HR每天要看成千上万份通过网上递交的简历,看到你的那份时都不知道猴年马月了。何况,你真的确定你的简历能在上万份简历里stand out? 这时内推就能帮你了。内部规定HR必须在收内推的一周内查看你的简历,必须在两周内给出答复。换句话说,你算是插队了。 可是这不能提高你被录取的成功率,实力不行的人也只是插队被拒。整个面试和普通流程都是一样的。最终决定录取与否的始终是你的背景,能力,和面试表现。
就像Web开发的layout,写简历时也应该考虑自己的简历布局。怎样划分简历内容板块,个人信息是居中还是居左,头像应该和个人信息一块居中还是和个人信息对称放置。建议参考身边学长或者hr发的简历模板。
这里以我的简历为例,供感兴趣的同学查看
前端开发工程师-北理工-软件工程-杨珏成
写简历切忌一股脑把自己的骄傲倔强都写上去,这样只会让面试官看不懂你的简历。
先把自己的全部简历素材整理成一个文本库,再根据应聘的不同职位精心挑选出与岗位匹配的简历内容,其中以相关性最高,最能突出个人能力的经历为先。这里推荐一个技术简历的最佳实践:
改良程序员的问题简历,从反模式到最佳实践
笔试没有什么窍门,我个人会刷Leetcode。要是能刷100道基本上面试题就不用愁,要是刷到200道就不用惧怕校招笔试了。
我在 附录2
中记录了自己遇到的一些笔试题,仅供参考。
任何人在第一次面对面试官的时候都会紧张,即使参加了多场面试,依然会在有的时候感觉控制不住自己的状态。克服这种紧张感,你就已经比多数面试者成功了一小半。
面试官永远都喜欢你临危不乱,面对难题依然能够掌握大局的样子。并且在未来任何场合都是如此。
如何克服紧张,我这里提供两点建议
面试的时候,面试官总是喜欢出一些看起来很刁难的问题:实现一个Vue双向绑定,写一个公平的洗牌算法,写一个css走马灯样式,获取某个字符串的全排列,写一个class的polyfill等等。
或者是深挖你的项目经历一步步追问技术细节,让你现场考虑如何实现更好的效果。
这种时候,你要意识到面试官是在考验你的逻辑思维,面试官的目的不是要你给出一个绝对完美的解决方案,而是想看到你如何运用一套好的逻辑思维来调用自己的知识库,最终给出你自己的思考路径。最重要的是这个过程中间的思考,一定要阐述给面试官。
接下来是整个面试中90%时间在干的事情,也就是对你的技术储备与工程能力的考核。
一般来说,大厂的前端校招会比其他中小企业更看重对面试者的全方位考核,如果你是科班出身,校招的技术考核会包括且不限于:
另外,不同的大厂也有不同的侧重点。
技术实力最顶尖的阿里淘系FED会对你的基础知识体系以及你简历上写到的技能展开一场惨绝人寰的刨根问底,而字节跳动则更看重你的实际工程经验以及对于软件系统架构的理解。
通过每家大厂的面试策略,你也可以侧面观察出这家企业的团队技术实力和业务发展方向。
我将技术面中遇到的所有知识点整理成了一张思维导图。建议一条一条仔细查阅,对于任何没有百分百把握的知识点,我都建议你把它整理到一个列表里,逐条梳理。
PS.标星的是非必须知识,可以略过:
对于不想看图的同学,我把它导出成了一份大纲,可以收藏到备忘录里。
大厂前端校招 - 知识体系
如果你通过了以上所有考核的话,恭喜你,你离获得offer基本只剩一步之遥了。
一般到这个时候,面试官会问你对自己的职业规划。
这个问题其实也是需要自己问题自己的,最好在投递简历的时候就想清楚:
还是那句话 —— 不想当架构师的前端不是好程序员(雾)。
如果面试没有复盘,你参加再多的面试也和没有参加一样。不要叹息面试中出的洋相或者咒骂面试官有多么反人类,不管多么受伤都要在面试结束后立刻复盘。
现场面试最好一出场就开始回忆面试流程&写备忘录,如果是电话面试可以录音下来重听一边,捋一捋面试官的问题和自己的回答,看看自己答得如何(答成了什么b样),有没有可能答得更好。
我在 附录1
中记录了每次参加校招时的复盘。
这里本来想仔细量化评测一下各家大厂面试流程中的面试体验,遗憾的是有些面试已经过去一段时间了,印象不是那么清晰,我担心我的评价会有失偏颇。所以就简单说一下在各家面试过程中让我印象深刻的一些事情吧。
# 6.8 阿里校招二面
)最近看到一篇文章《前端深水区》感触颇深,技术岗的最终出路一定是建立技术壁垒和影响业务决策。
在面试B站的时候,也遇到了一个让我陷入了思考的问题,面试官当时问我:“我对你的职业规划印象很好,你打算怎样去实现它呢?我给你一分钟的时间仔细思考这个问题。”
最后我回答了三句话:
与大家共勉。
符号 | 含义 |
---|---|
√ | 现场回答正确 |
× | 现场回答错误 |
? | 现场无法判断是否答对 |
追问 | 对于父问题的追问 |
() | 我在面试现场的回答思路 |
()右边 | 最合适的回答(或对现场回答的补充) |
补充 | 面试官亲自对现场回答的补充 |
问 | 我向面试官提出的问题 |
PS. 并不是所有试题都包含以上符号,因为都是现场回忆记录,所以有很多不全的地方。
www.toutiao.com
转为 com.toutiao.www
√输入:[1, 2, [3, 4], 5, 6, [7, 8], 9] 输出: [[3, 4, 1, 2, 5, 6][7, 8 ,9]]
输入:contentType输出:content_type
坑点:没有准备耳机,视频面试官声音比较小,一开场乱了节奏
后续内容会在掘金原文中更新
小明和小红用字符串压缩通信。字符串压缩规则是:如果有连续重复的字符串比如ABCABCABC就缩写成[3|ABC]。现有压缩后的字符串,设计一个解压程序还原字符串。
输入:HG[3|B[2|CA]]F输出:HGBCACABCACABCACAF
需要优化内存,我之所以87.5就是因为内存溢出MLE了,正在考虑用栈结构重写一次。
string decode(string s) { string res = "", ans = ""; int len, start , end; int time, counting; time = 0, counting = 1; len = s.size(); for (int i = 0; i < len; i++) { if (s[i] == '[') { start = i; for (i = len; s[i] != ']'; i--); end = i; res += decode(s.substr(start + 1, end - start - 1)); i++; } if (counting && s[i] >= '0' && s[i] <= '9') { time = time * 10 + (s[i] - '0'); } else if (s[i] == '|') { counting = 0; } else { res += s[i]; } } char tmp = res[res.size() - 1]; if (tmp == '\0') { res = res.substr(0, res.size() - 1); } if (time > 0) { for (int i = 0; i < time; i++) { ans.append(res); } } else { ans = res; } return ans;}
int main(){ string s; cin >> s; cout << decode(s) << endl; return 0;}
判断一个ip地址是不是私有的 已知私有IP范围是:
10.0.0.0 - 10.255.255.255172.16.0.0-172.16.255.255192.168.0.0-192.168.255.255127.0.0.0/8 # 注意!这里是一个巨坑,0/8的意思代表子网掩码255.255.255.0,也就是最后8位可以有动态范围,这是一种简写方法,但是腾讯并没有说明其含义,可能也是一处考察。
输入:0.0.0.0输出:false
function isPrivate(ip){ // TODO let ipVal = ip.split('.'); ipVal[0] = Number(ipVal[0]); ipVal[1] = Number(ipVal[1]); ipVal[2] = Number(ipVal[2]); ipVal[3] = Number(ipVal[3]); if (ipVal[0] == 10) { if (ipVal[1] >= 0 && ipVal[1] <= 255) { if (ipVal[2] >= 0 && ipVal[2] <= 255) { if (ipVal[3] >= 0 && ipVal[3] <= 255) { return true; } } } } if (ipVal[0] == 172) { if (ipVal[1] >= 16 && ipVal[1] <= 31) { if (ipVal[2] >= 0 && ipVal[2] <= 255) { if (ipVal[3] >= 0 && ipVal[3] <= 255) { return true; } } } } if (ipVal[0] == 192) { if (ipVal[1] == 168) { if (ipVal[2] >= 0 && ipVal[2] <= 255) { if (ipVal[3] >= 0 && ipVal[3] <= 255) { return true; } } } } if (ipVal[0] == 127) { if (ipVal[1] == 0) { if (ipVal[2] == 0) { if (ipVal[3] >= 0 && ipVal[3] <= 8) { return true; } } } } return false;}
把一个由 - 或 _ 或 @ 连接的变量词组转换成驼峰写法
输入:content-type输出:contentType
function camel(str) { // TODO let ans = ""; let upper = false; for (let index = 0; index < str.length; index++) { const element = str[index]; if (element == '_' || element == '-' || element == '@') { upper = true; } else { if (upper) { ans += element.toUpperCase(); } else { ans += element; } upper = false; } } return ans;};
企鹅星球上一天有N(<200000)个小时(时间不包含0点),对应N个时区,当第1时区一点的时候第2时区已经两点了,以此类推 每个时区有Ai个人,每个时区上的人只有在[u,v)时间内有空,现在想要让尽可能多的人开会,给出开会时第一时区的时刻
输入:32 5 61 3输出:3
时区的对应有一点绕,我一开始理解成后一个时区比前一个时区落后,实际上是超前的,每后一个时区比前一个时区快1个小时,解决掉这个问题就没有大问题了。另外要考虑一下时间复杂度的问题,我的优化比较差,最坏复杂度是O(n2/2)
int main() { int n, u, v, len, pos; long long ans, tmp; cin >> n; vector<int> a(n, 0); for (int i = 0; i < n; i++) { cin >> a[i]; } cin >> u >> v; u--; v--; len = v - u; pos = 0; if (len < n / 2) { ans = 0; for (int i = 0; i < n; i++) { tmp = 0; for (int j = 0; j < len; j++) { tmp += a[(i + j) % n]; } if (tmp > ans || (tmp == ans && ((n + u - pos) % n < (n + u - pos) % n))) { ans = tmp; pos = i; } } } else { ans = INF; for (int i = 0; i < n; i++) { tmp = 0; for (int j = 0; j < n - len; j++) { tmp += a[(i + j) % n]; } if (tmp < ans || (tmp == ans && ((n + u - pos) % n < (n + u - pos) % n))) { ans = tmp; pos = i; } } } cout << (n + u - pos) % n + 1 << endl; return 0;}
第一题的思路比较简单,就是辗转相除法,用字符串存储大数,然后分段辗转相除
辗转相除法: 假如有两个正整数p1,p2,其中p1>p2,那么必然存在两个自然数k,b,使得p1=k*p2。如果p1,p2的最大公约数是p3,那么p2,b的最大公约数也是p3。例如gcb(55,30)=gcb(25,30)=gcb(25,5)
题目:在一个最大长度200000的数组中,分别求出长度从1到n的子序列中最大值的最小值
输入:61 8 7 5 4 2输出:1 4 5 7 8 8
简单来说,就是把一个数组进行连续子序列的划分,从长度为1的子序列开始划分,每次划分子序列后,求出每个子序列的最大值,再求出所有这些最大值中最小的那个,一直到长度为n的子序列(序列本身)。
这题一开始把我看绕了,其实就是一道标准的DP题,然而我最后做的这题,考完才写出来。。。这次笔试基本是按照最差的答题顺序来的,估计跪了。
状态转移方程可以这样想出来:
设 dp[j][i]
是从数组第 j
个数字开始的长度为 i
的子序列的最大值,当长度i=0(实际长度应该为1,从0开始方便些)时, dp[j][0]
等于数字本身 num[j]
,从i=1开始,dp[j][i]的长度等于 MAX(dp[j][i-1],dp[j+1][i-1])
也就是前后相邻的两个长度为i-1的子序列最大值中的最大值。
这题要求的是同一划分长度下所有最大值的最小值,所以在计算dp数组的同时还要计算这个值是否为当前划分长度的最小值,于是定义一个min数组,长度100000,先初始化成最大数值,每次计算 dp[j][i]
的时候与 min[i]
比较哪个值更小,一趟下来就能得到最小值了。
#include <stdio.h>#include <stdlib.h>#include <math.h>#include <string.h>#include <algorithm>#define MAX(x,y) ((x) > (y) ? (x) : (y))#define INF 0x7FFFFFFFusing namespace std;int num[100000] = { 0 };int (*dp)[100000];int main(){ int n; int min[100000] = { 0 }; scanf("%d", &n); dp = (int (*)[100000])malloc(n * 100000 * sizeof(int)); for (int i = 0; i < n; i++) { scanf("%d", &num[i]); min[i] = INF; } for (int i = 0; i < n; i++) { for (int j = 0; j < n - i; j++) { if (i == 0) { dp[j][0] = num[j]; } else { dp[j][i] = MAX(dp[j][i - 1], dp[j + 1][i - 1]); } if (dp[j][i] < min[i]) { min[i] = dp[j][i]; } i = i; } } for (int i = 0; i < n; i++) { if (i>0) { printf(" "); } printf("%d", min[i]); } printf("\n"); return 0;}
一个数组中,奇偶数可互换,求任意次互换后字典序最小的数组序列。
个人思路:没有特别好的想法
给定一个长度M(<=100000)的数组,然后输入N(<=100000)个整数,每次将数组中所有大于等于该整数的元素减一,并输出改变了多少个元素,要求时间性能小于1s。
用二分查找结果70%结果都TLE了,经过分析认为主要是遍历数组进行减一的操作太费时间(O(n^2)的复杂度)后来考虑用一个数组储存更新过的下标分界位置来绕过遍历减一的环节,然而没写完。
给定暑假时间X天(<=1000),游戏数量N个(<=11),接下来N行给定每种游戏需要花费的天数(Ai),以及通关该游戏带来的成就点数(Bi),求:在暑假X天里能够达成的最高成就点数。
#include <iostream>#include <vector>#include <cassert>#include <algorithm>using namespace std;
// 需要填充一个容量为X的背包,使得成就点数最大class Knapsack01 {
private: vector<vector<int>> memo;
// 用 [0...index]的物品,填充容积为c的背包的最大价值 int bestValue(const vector<int> &w, const vector<int> &v, int index, int c) {
if (c <= 0 || index < 0) return 0;
if (memo[index][c] != -1) return memo[index][c];
int res = bestValue(w, v, index - 1, c); if (c >= w[index]) res = max(res, v[index] + bestValue(w, v, index - 1, c - w[index])); memo[index][c] = res; return res; }
public: int knapsack01(const vector<int> &w, const vector<int> &v, int C) { assert(w.size() == v.size() && C >= 0); int n = w.size(); if (n == 0 || C == 0) return 0;
memo.clear(); for (int i = 0; i < n; i++) memo.push_back(vector<int>(C + 1, -1)); return bestValue(w, v, n - 1, C); }};
int main() {
// X为暑假天数,N为游戏数量 int X, N; cin >> X >> N;
int w, v; // vs存的是价值(成就点数) // ws存的是每一件物品的重量(天数) vector<int> vs, ws; for (int i = 0; i < N; i++) { cin >> w >> v; vs.push_back(v); ws.push_back(w); }
cout << Knapsack01().knapsack01(ws, vs, X) << endl;
return 0;}
PS.这题我特么写成完全背包了,其实是01背包,结果只对50%。
输入指令集长度M和指令操作长度N 接下来输入M个指令(字符串)=》指令值(字符串)的映射关系 然后随机输入N个指令,要求输出对应指令值。
最简单的用c++ map容器,然而忘记map写法,耽误大量时间,超级遗憾。
#include <iostream>#include <string>#include <map>using namespace std;
int main(){ map<string, string> ops; int x, y; cin >> x >> y; for (int i = 0; i < x; i++) { string a, b; cin >> a >> b; ops[a] = b; } for (int i = 0; i < y; i++) { string op; cin >> op; cout << ops[op] << endl; }}
给定N块钱,M种水果,每种水果价格Pi,其中有X种特别喜欢的水果,给定不同水果喜欢程度的排序,并要求排序靠前的水果购买量不得小于靠后的,求所有把钱花光的可能性,结果对10000007取模。
跪了...
小明定了n个闹钟,他只能在闹钟响起时出发去学校,每个闹钟时间分别为hi点mi分,小明家到学校要x分钟,学校上课时间a点b分 (0-24小时,0-59分钟),求他最晚几点起
输入:3 //定了几个闹钟5 0 //第1个闹钟的小时数和分钟数6 0 //第2个闹钟的小时数和分钟数7 0 //第3个闹钟的小时数和分钟数59 //到学校要多少分钟6 59 //上课的小时数和分钟数输出:6 0 //最晚的起床时间
纯智障思路,自定义结构体存储闹钟时间,全部输入后对闹钟时间从晚到早排序,接下来从前往后遍历闹钟时间,计算从当前时刻出发到学校的时间,输出第一个能够到达学校的,由于算法很粗劣,很明显被卡边界了,没时间管了直接看下一题。
struct Time{ int h; int m; friend bool operator < (Time a, TIme b){ if(a.h == b.h){ return a.m > b.m; } return a.h > b.h; }}int main(){ int n, x, a, b, rest; cin >> n; Time* time = (Time*)malloc(n * sizeof(Time)); for (int i = 0; i < n; i++) { cin >> time[i].h >> time[i].m; } sort(time, time + n); cin >> x; cin >> a >> b; for (int i = 0; i < n; i++) { rest = 0; if (time[i].h < a || time[i].h == a && time[i].m < b) { rest = (a - time[i].h) * 60 + b - time[i].m; if (rest >= x) { cout << time[i].h << ' ' << time[i].m << endl; break; } } } return 0;}
小明和小红采用密码加密通信,每次通信有固定的明文长度n和加密次数k。比如:密码二进制明文是1001010,加密次数是4,则每次将密文右移1位与明文做异或操作,总共位移3次(k=4, 所以k - 1 = 3)
输入:7 4 // n k1110100110 //密文输出:1001010 //明文
解释:1001010----1001010----1001010----1001010
加密次数为4,故对于明文右移4-1=3轮,每轮与当前密文进行一次异或,故1001010对应密文为1110100110
一道标准的异或数学题,不知道该怎么归类,有一点考数学的感觉,看几眼就能看出规律了直接上代码
简单讲一下思路:
首先密文和明文第1位是一样的,看一下上方样例里的解释就懂了。然后考虑第2到k-1位,可以发现这一段的每一位都是由前一位密文的异或结果再与当前位明文异或得到的。
接下来考虑第k到n-1位,观察规律可以发现这一段的每一位都是由前一位密文与第i-k位明文异或得到的结果再与当前位明文异或得到的。如何消除异或影响大家应该都能理解,因此只要把参与异或的部分再与密文异或一下即可得到明文。
int main() { int n, k, tmp; string s,ans=""; cin >> n >> k; cin >> s; ans += s[0]; for (int i = 1; i < k; i++) { tmp = (int)(s[i] - '0') ^ (int)(s[i - 1] - '0'); ans += tmp + '0'; } for (int i = k; i < n; i++) { ans += (int)(s[i] - '0') ^ (int)(s[i - 1] - '0') ^ (int)(ans[i - k] - '0') + '0'; } cout << ans; return 0;}
王大锤要给员工发工资,员工从左到右坐成一排,每个员工知道彼此的资历,每个员工只知道自己左右员工的工资,如果某员工比左边或右边的人资历老,那他一定比这个人工资高100元,每个人最低工资100元,求王大锤最低给多少工资。
输入:4 //几个员工3 9 2 7 //员工顺序以及对应的资历输出:600 //100元,200元,100元,200元
61 2 3 4 5 62100 //100,200,300,400,500,600
51 1 1 1 1500 //100,100,100,100,100
81 2 3 4 3 2 3 41800 //100 200 300 400 200 100 200 30083 4 3 4 3 4 3 41200 //100 200 100 200 100 200 100 20051 2 3 4 1 1100 //100 200 300 400 500
广度优先搜索,可以把员工序列看作一棵多根树,每个工资最低的员工就是根节点,一个员工的工资其实就是他在多根树里的深度,
首先在输入的时候找到比左右资历都年轻的员工入队,每次从队列pop一个员工,然后判断该员工的最小工资,然后判断左右员工是否可以入队,直到所有员工出队
int main() { int n, now; long long ans = 0; cin >> n; if (n == 0) { cout << 0 << endl; return 0; } vector<int> epy(n, 0), depth(n, 0); queue<int> sal; for (int i = 0; i < n; i++) { cin >> epy[i]; if (i > 1 && epy[i - 1] <= epy[i - 2] && epy[i - 1] <= epy[i]) { depth[i - 1] = 1; sal.push(i - 1); } } if (epy[0] <= epy[1]) { depth[0] = 1; sal.push(0); } if (epy[n - 1] <= epy[n - 2]) { depth[n - 1] = 1; sal.push(n - 1); } while (!sal.empty()) { now = sal.front(); int left = (now > 0 && epy[now-1] < epy[now]) ? depth[now - 1] : 0; int right = (now < n - 1 && epy[now + 1] < epy[now]) ? depth[now + 1] : 0; sal.pop(); if (depth[now] == 0) { depth[now] = max(left, right) + 1; } //left if (now > 0 && depth[now - 1] == 0 && (now == 1 || epy[now - 2] > epy[now - 1] || depth[now - 2] > 0)) { sal.push(now - 1); } //right if (now < n - 1 && (depth[now + 1] == 0) && (now == n - 2 || epy[now + 2] > epy[now + 1] || depth[now + 2] > 0)) { sal.push(now + 1); } } for (auto salary : depth) { ans += salary; } cout << ans * 100 << endl;}