抖音JAVA工程师_字节跳动抖音社招后台开发工程师面经

最近参加了字节跳动的后台开发工程师面试,记录一下面经。(ps:社招两年经验)

1. 一面(1小时)

一面主要是基础考察,包括简历中提到的以及一些标准的基础问题。

VUE和React区别(因为简历中提到了VUE)

VUE的内部机制(问一下前端技术是因为我简历提到自己是全栈工程师,正常情况下是不会出现前端问题的)

CRSF的机制

Spring IOC和BEAN循环依赖

Kafka和Cassandra内部机制(简历中提及)

算法题:有 2n 个人,序号为 0 到 2n-1,要求两两握手,但是握手不能存在交叉线,求最后一

共存在多少种握手可能,写出 f(2n)表达式。

思路:假设0号和i号点握手,那么整张图就分为了0 ~ i-1和i+1 ~ 2n这两部分。

算法题:给定一个二维整数矩阵,找出最长递增路径的长度。对于每个单元格,你可以往上,下,左,右四个方向移动。 你不能在对角线方向上移动或移动到边界外(即不允许环绕)。

思路:记忆化DFS

public class Solution {

private static final int[][] dirs = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};

public int longestIncreasingPath(int[][] matrix) {

if (matrix == null || matrix.length == 0) {

return 0;

}

int m = matrix.length, n = matrix[0].length;

int[][] cache = new int[m][n];

int res = 1;

for (int i = 0; i < m; ++i) {

for (int j = 0; j < n; ++j) {

res = Math.max(res, dfs(matrix, i, j, cache));

}

}

return res;

}

private int dfs(int[][] matrix, int i, int j, int[][] cache) {

if (cache[i][j] != 0) {

return cache[i][j];

}

int max = 1;

int m = matrix.length, n = matrix[0].length;

for (int[] dir : dirs) {

int x = i + dir[0], y = j + dir[1];

if (x >= 0 && x < m && y >= 0 && y < n && matrix[i][j] > matrix[x][y]) {

max = Math.max(max, 1 + dfs(matrix, x, y, cache));

}

}

cache[i][j] = max;

return max;

}

}

在另一个事业部一面时,遇到了三个水壶的问题:三个水壶,粉笔为 8L,3L,5L,给一个 num,判断能否量出这个指定 num 的水。

思路:BFS穷举所有可能性,直到出现目标水量。

public class ThreeBottles {

private int[] capacity;

public ThreeBottles() {

capacity = new int[] { 8, 5, 3 };

}

public boolean canMeasure(int n) {

Set visited = new HashSet<>();

Queue queue = new LinkedList<>();

int initialState = getState(new int[] { 8, 0, 0 });

queue.add(initialState);

visited.add(initialState);

while (!queue.isEmpty()) {

int state = queue.poll();

if (match(state, n)) return true;

int[] water = getWater(state);

for (int i = 0; i < 3; i++) {

for (int j = 0; j < 3; j++) {

int next = transfer(i, j, water);

if (next != -1 && !visited.contains(next)) {

queue.add(next);

visited.add(next);

}

}

}

}

return false;

}

private int getState(int[] water) {

return water[0] << 8 | water[1] << 4 | water[2];

}

private int[] getWater(int state) {

return new int[] { state >>> 8 & 15, state >>> 4 & 15, state & 15 };

}

private boolean match(int state, int n) {

return (state >>> 8 & 15) == n || (state >>> 4 & 15) == n || (state & 15) == n;

}

private int transfer(int i, int j, int[] water) {

int[] next = new int[] { water[0], water[1], water[2] };

if (i != j && water[i] > 0 && water[j] < capacity[j]) {

int transmission = Math.min(water[i], capacity[j] - water[j]);

next[i] -= transmission;

next[j] += transmission;

return getState(next);

}

return -1;

}

}

2. 二面(1小时)

项目介绍(半小时)

准备好一些工作中解决的问题,尽可能详细的阐述给面试官,告诉他你主要解决了什么问题。

系统设计题:上海地铁线纵横交错(线路与线路之间存在交叉,并且每一条线路站与站之间的发车间隔可能是不一样的),现在做一个 app,输入是起点和终点已经出发时间,输出一条耗时最短的线路。

从数据库表,到缓存,到算法的设计。

3. 三面(40分钟)

三面是抖音上海负责人的面试,因此着重在系统设计。

项目介绍(15分钟)

系统设计:抖音里每秒都产生大量视频,现在要求持续计算一段时间内的 top k 个最热门的视频,你怎么设计。

这个问题类似于下面这个问题:

实现前5分钟,1小时,24小时内分享最多的feed

从算法的角度,可以简单的称之为 Top K Frequent Elements in Recent X mins。算法的角度,本质就是设计一个数据结构,支持给某个key的count+1(有一个post被分享了),给某个key的count-1(有一个分享的计数已经过期了),然后查询Top k。

做法是维护一个有序序列(用链表来维护),每个链表的节点的key是 count,value是list of elements that has this count,也用linked list串起来。 比如 a出现2次,b出现3次,c出现2次,d出现1次。那么这个链表就是:

{3: [b]} --> {2: [a ->c]} --> {1: [d]}

然后另外还需要一个hashmap,key是element,value是这个element在链表上的具体位置。

因为每一次的操作都是 count + 1和 count - 1,那么每次你通过 hashmap 找到对应的element在数据结构中的位置,+1的话,就是往头移动一格,-1的话,就是往尾巴移动一格。总而言之复杂度都是 O(1)。当你需要找 Top K 的时候,也是 O(k)的时间可以解决的。

工程角度的优化:

如果我要去算最近5分钟的数据,我就按照1秒钟为一个bucket的单位,收集最近300个buckets里的数据。如果是统计最近1小时的数据,那么就以1分钟为单位,收集最近60个Buckets的数据,如果是最近1天,那么就以小时为单位,收集最近24小时的数据。那么也就是说,当来了一个某个帖子被分享了1次的数据的时候,这条数据被会分别存放在当前时间(以秒为单位),当前分钟,当前小时的三个buckets里,用于服务之后最近5分钟,最近1小时和最近24小时的数据统计。

你可能会疑惑,为什么要这么做呢?这么做有什么好处呢?这样做的好处是,比如你统计最近1小时的数据的时候,就可以随着时间的推移,每次增加当前分钟的所有数据的统计,然后扔掉一小时里最早的1分钟里的所有数据。这样子就不用真的一个一个的+1或者-1了,而是整体的 +X 和 -X。当然,这样做之后,前面的算法部分提出来的数据结构就不work了,但是可以结合下面提到的数据抽样的方法,来减小所有候选 key 的数目,然后用普通的 Top K 的算法来解决问题。

另外,在分布式环境下,哪些帖子被分享了多少次这些数据,首先在 web server 中进行一次缓存,也就是说web server的一个进程接收到一个分享的请求之后,比如 tweet_id=100 的tweet被分享了。那么他把这个数据先汇报给web server上跑着的 agent 进程,这个agent进程在机器刚启动的时候,就会一直运行着,他接受在台web server上跑着的若干个web 进程(process) 发过来的 count +1 请求。

这个agent整理好这些数据之后,每隔510秒汇报给中心节点。这样子通过510s的数据延迟,解决了中心节点访问频率过高的问题。

还可以通过数据抽样进行优化。因为那些Top K 的post,一定是被分享了很多很多次的,所以可以进行抽样记录。如果是5分钟以内的数据,就不抽样,全记录。如果是最近1小时,就可以按照比如 1/100 的概率进行 sample。

最后是使用Cache进行缓存计算结果。对于最近5分钟的结果,每隔5s才更新一次。对于最近1小时的结果,每隔1分钟更新一次。对于最近24小时的结果,每隔10分钟才更新一次。用户需要看结果的时候,永远看的是 Cache 里的结果。另外用一个进程按照上面的更新频率去逐渐更新Cache。

总结:算法方面使用LFU算法来解决。而工程方面使用分段统计,抽样法和Cache缓存进行优化。

4. 总结

网络和操作系统需要复习,复习一些常见的网上都能搜到的字节考题就行,不用太深入。

算法 leetcode 刷 200~300 道差不多了,另外面试前搜一下字节考过的算法题,很有可能遇到相同的。

系统设计题就看平时积累,有想法就说出来,即使不是最优解也要说出来。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值