本来是通过闪电内推投的南京的后端开发实习生,HR打电话说南京没有岗位了,帮我转投的上海产品研发和工程架构部。
一共三面技术面,没有HR面,直接offer。
一面
聊项目,项目里用到一个开源路由软件quagga,问我和传统路由软件(华为)的区别;
项目开发遇到的难点、项目开发过程等等;
写题目,给定字符串s1,s2,target,判断target是否是s1、s2的交错组成,比如s1 = "abcd", s2 = "efgh", target = "aebfghcd",target中字符由s1和s2中字符构成,并且s1/s2中字符在target中保持原来的顺序。
上来就想到了归并排序的merge思路,只要按顺序看target是否是s1/s2的merge就行了,比如target第一个字符a和s1第一个字符a相同,就比较s1剩余的部分/s2以及target剩余部分,否则比较s1/s2剩余的部分以及target剩余部分,case通过率40%
发现问题,s1 = "abc", s2 = "aef", target = "aefabc",如果第一个a和s1的a匹配,那么target剩余的部分无法和s1剩余部分以及s2匹配,然而如果第一个a和s2的a匹配,则能匹配成功,因此遇到相同字符时两种情况都要考虑。
但是已经来不及改了,还被面试官吐槽直接把逻辑写到main函数中,写算法的坏习惯。。。
面试完重新写了, AC,下面是编辑器重新写的,可能会有错误。
bool help(string s1, int i, string s2, int j, string target, int k, vector<vector<int>>& dp) {
if (k == target.size())
return true;
if (dp[i][j] != -1)
return dp[i][j];
bool res = false;
if (i < s1.size() && s1[i] == target[k]) {
res |= help(s1, i + 1, s2, j, target, k + 1);
if (res)
return dp[i][j] = res;
}
if (j < s2.size() && s2[j] == target[k])
res |= help(s1, i, s2, j + 1, target, k + 1);
return dp[i][j] = res;
}
bool isCross(string s1, string s2, string target) {
int l1 = s1.size(), l2 = s2.size(), l = target.size();
// s1和s2长度之和和target长度不相等
if (l1 + l2 != l)
return false;
// 记录中间状态结果,减少重复计算
vector<vector<int>> dp(l1 + 1, vector<int>(l2 + 1, -1));
dp[l1][l2] = 1;
return help(s1, 0, s2, 0, target, 0, dp);
}
最后问了一下C++的多态就结束了,面试完面试小哥让我好好想想代码怎么写,以为大概率凉凉,结果第二天HR约了二面。
二面
二面的面试官非常友好,整个面试下来体验不错。
自我介绍,介绍简历上2个项目;
C++11 特性介绍;
右值引用和移动语义;
智能指针;
写参数是指针的模板函数;
template<class T>
void func(T* p) {}
linux网络指令,losf/netstat/tcpdump;
TCP/UDP,一揽子问题 + 应用场景;
进程间通信的几种方式,大概介绍;
算法题,给定一个数src,一个目标数target,每个数每次可以加上自己的因数(除1和本身)跳到下一个数,问src最少用几次跳到target,比如4->24,最短路径长度是5(4->6->8->12->18->24,最短路径可能不知一条),动态规划,面试官提示了思路完成。
提问环节,介绍部门所用技术栈,面试官讲的非常详细,游戏中台搭建相关内容,云原生技术等等。
vector<int> factors(int n) {
vector<int> res;
for (int i = 2; i * i <= n; ++i) {
if (n % i == 0) {
res.push_back(i);
res.push_back(n / i);
}
}
return res;
}
int minSteps(int src, int target) {
vector<int> dp(target + 1, INT_MAX);
dp[src] = 0;
// 从src开始遍历直达target
// 在当前数基础上加上每个因数,结果可以从当前位置再跳一步获得
for (int i = src; i < target; ++i) {
for (int factor : factors(i)) {
dp[i + factor] = min(dp[i] + 1, dp[i + factor]);
}
}
// 不可达
if (dp[target] == INT_MAX)
return -1;
return dp[target];
}
三面
问的问题十分全面,但相对而言比较基础。
手写算法题,2道,都是剑指offer原题
1.随机链表复制,每个链表有一个随机指向的指针rand。直接照书上思路写的话比较麻烦,需要先复制每个节点插到原来节点后面,而后确定新节点指针指向,最后将新节点分离出来做成新的链表。
于是我用的map来代替,原节点为key映射到复制后的节点,代码如下:
class Node{
public:
int val;
Node* next;
Node* rand;
Node(int v) : val(v), next(nullptr), rand(nullptr) {}
};
Node* copyRandomList(Node* head) {
if (head == nullptr)
return nullptr;
map<Node*, Node*> m;
Node* cur = head;
while (cur != nullptr) {
m[cur] = new Node(cur->val);
cur = cur->next;
}
cur = head;
while (cur != nullptr) {
m[cur]->next = m[cur->next];
m[cur]->rand = m[cur->rand];
cur = cur->next;
}
return m[head];
}
2.顺时针打印矩阵,借鉴左程云《程序员面试指南》上的写法,清晰易懂
// 每次打印矩阵一圈
void printCirc(vector<vector<int>>& matrix, int tr, int tc, int dr, int dc) {
int i = tr, j = tc;
while (j < dc) {
cout << matrix[tr][j] << " ";
++j;
}
while (i < dr) {
cout << matrix[i][dc] << " ";
++i;
}
while (j > tc) {
cout << matrix[dr][j] << " ";
--j;
}
while (i > tr) {
cout << matrix[i][tc] << " ";
--i;
}
}
void printMat(vector<vector<int>>& matrix) {
int tr = 0, tc = 0; // 左上角行列坐标
int dr = matrix.size() - 1, dc = matrix[0].size() - 1; // 右下角行列坐标
while (tr <= dr && tc <= dc) {
printCirc(matrix, tr++, tc++, dr--, dc--);
}
}
select/epoll/poll区别以及使用注意点;
线程和进程;
100亿个访问某网站的用户id,统计出现次数最多的10个用户,老生常谈大数问题;
shell脚本写找出一个文件中出现次数最多的10个用户id,我把出现次数前10的次数显示了出来。。面试官看命令没啥问题就过了。
sort | uniq -c | awk 'print $1' | sort -rn | head -10
问了对docker底层原理的理解。答了底层的命名空间隔离技术,network namespace以及与传统虚拟化技术的区别。
非常基础的记不太清了,后续有补充会更新。
大概隔了一周接到offer call和正式邮件,由于已经接受了鹅厂的offer,没有接受这个offer,希望以后有机会能去字节学习工作。
目前已经写了腾讯和字节的面经,后续会继续更新百度、网易、网易雷火(均已offer),华为(流程中),阿里微软(挂)的面经,也算作对春招的一次认真总结,希望对大家有所帮助。