字節(jié)跳動抖音社招后臺開發(fā)工程師面經(jīng)

最近參加了字節(jié)跳動的后臺開發(fā)工程師面試,記錄一下面經(jīng)。(ps:社招兩年經(jīng)驗)

1. 一面(1小時)

一面主要是基礎(chǔ)考察,包括簡歷中提到的以及一些標準的基礎(chǔ)問題。

  1. VUE和React區(qū)別(因為簡歷中提到了VUE)

  2. VUE的內(nèi)部機制(問一下前端技術(shù)是因為我簡歷提到自己是全棧工程師,正常情況下是不會出現(xiàn)前端問題的)

  3. CRSF的機制

  4. Spring IOC和BEAN循環(huán)依賴

  5. Kafka和Cassandra內(nèi)部機制(簡歷中提及)

  6. 算法題:有 2n 個人,序號為 0 到 2n-1,要求兩兩握手,但是握手不能存在交叉線,求最后一
    共存在多少種握手可能,寫出 f(2n)表達式。

思路:假設(shè)0號和i號點握手,那么整張圖就分為了0 ~ i-1和i+1 ~ 2n這兩部分。

  1. 算法題:給定一個二維整數(shù)矩陣,找出最長遞增路徑的長度。對于每個單元格,你可以往上,下,左,右四個方向移動。 你不能在對角線方向上移動或移動到邊界外(即不允許環(huán)繞)。

思路:記憶化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;
    }
}
  1. 在另一個事業(yè)部一面時,遇到了三個水壺的問題:三個水壺,粉筆為 8L,3L,5L,給一個 num,判斷能否量出這個指定 num 的水。

思路:BFS窮舉所有可能性,直到出現(xiàn)目標水量。

public class ThreeBottles {

    private int[] capacity;

    public ThreeBottles() {
        capacity = new int[] { 8, 5, 3 };
    }

    public boolean canMeasure(int n) {
        Set<Integer> visited = new HashSet<>();
        Queue<Integer> 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小時)

  1. 項目介紹(半小時)

準備好一些工作中解決的問題,盡可能詳細的闡述給面試官,告訴他你主要解決了什么問題。

  1. 系統(tǒng)設(shè)計題:上海地鐵線縱橫交錯(線路與線路之間存在交叉,并且每一條線路站與站之間的發(fā)車間隔可能是不一樣的),現(xiàn)在做一個 app,輸入是起點和終點已經(jīng)出發(fā)時間,輸出一條耗時最短的線路。

從數(shù)據(jù)庫表,到緩存,到算法的設(shè)計。

3. 三面(40分鐘)

三面是抖音上海負責人的面試,因此著重在系統(tǒng)設(shè)計。

  1. 項目介紹(15分鐘)

  2. 系統(tǒng)設(shè)計:抖音里每秒都產(chǎn)生大量視頻,現(xiàn)在要求持續(xù)計算一段時間內(nèi)的 top k 個最熱門的視頻,你怎么設(shè)計。

這個問題類似于下面這個問題:

實現(xiàn)前5分鐘,1小時,24小時內(nèi)分享最多的feed

從算法的角度,可以簡單的稱之為 Top K Frequent Elements in Recent X mins。算法的角度,本質(zhì)就是設(shè)計一個數(shù)據(jù)結(jié)構(gòu),支持給某個key的count+1(有一個post被分享了),給某個key的count-1(有一個分享的計數(shù)已經(jīng)過期了),然后查詢Top k。

做法是維護一個有序序列(用鏈表來維護),每個鏈表的節(jié)點的key是 count,value是list of elements that has this count,也用linked list串起來。 比如 a出現(xiàn)2次,b出現(xiàn)3次,c出現(xiàn)2次,d出現(xiàn)1次。那么這個鏈表就是:

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

然后另外還需要一個hashmap,key是element,value是這個element在鏈表上的具體位置。
因為每一次的操作都是 count + 1和 count - 1,那么每次你通過 hashmap 找到對應的element在數(shù)據(jù)結(jié)構(gòu)中的位置,+1的話,就是往頭移動一格,-1的話,就是往尾巴移動一格??偠灾畯碗s度都是 O(1)。當你需要找 Top K 的時候,也是 O(k)的時間可以解決的。

算法實現(xiàn)請參考:https://github.com/cheergoivan/leetcode/blob/master/src/leetcode/problem460/LFUCache.java

工程角度的優(yōu)化:

如果我要去算最近5分鐘的數(shù)據(jù),我就按照1秒鐘為一個bucket的單位,收集最近300個buckets里的數(shù)據(jù)。如果是統(tǒng)計最近1小時的數(shù)據(jù),那么就以1分鐘為單位,收集最近60個Buckets的數(shù)據(jù),如果是最近1天,那么就以小時為單位,收集最近24小時的數(shù)據(jù)。那么也就是說,當來了一個某個帖子被分享了1次的數(shù)據(jù)的時候,這條數(shù)據(jù)被會分別存放在當前時間(以秒為單位),當前分鐘,當前小時的三個buckets里,用于服務(wù)之后最近5分鐘,最近1小時和最近24小時的數(shù)據(jù)統(tǒng)計。

你可能會疑惑,為什么要這么做呢?這么做有什么好處呢?這樣做的好處是,比如你統(tǒng)計最近1小時的數(shù)據(jù)的時候,就可以隨著時間的推移,每次增加當前分鐘的所有數(shù)據(jù)的統(tǒng)計,然后扔掉一小時里最早的1分鐘里的所有數(shù)據(jù)。這樣子就不用真的一個一個的+1或者-1了,而是整體的 +X 和 -X。當然,這樣做之后,前面的算法部分提出來的數(shù)據(jù)結(jié)構(gòu)就不work了,但是可以結(jié)合下面提到的數(shù)據(jù)抽樣的方法,來減小所有候選 key 的數(shù)目,然后用普通的 Top K 的算法來解決問題。

另外,在分布式環(huán)境下,哪些帖子被分享了多少次這些數(shù)據(jù),首先在 web server 中進行一次緩存,也就是說web server的一個進程接收到一個分享的請求之后,比如 tweet_id=100 的tweet被分享了。那么他把這個數(shù)據(jù)先匯報給web server上跑著的 agent 進程,這個agent進程在機器剛啟動的時候,就會一直運行著,他接受在臺web server上跑著的若干個web 進程(process) 發(fā)過來的 count +1 請求。

這個agent整理好這些數(shù)據(jù)之后,每隔510秒?yún)R報給中心節(jié)點。這樣子通過510s的數(shù)據(jù)延遲,解決了中心節(jié)點訪問頻率過高的問題。

還可以通過數(shù)據(jù)抽樣進行優(yōu)化。因為那些Top K 的post,一定是被分享了很多很多次的,所以可以進行抽樣記錄。如果是5分鐘以內(nèi)的數(shù)據(jù),就不抽樣,全記錄。如果是最近1小時,就可以按照比如 1/100 的概率進行 sample。

最后是使用Cache進行緩存計算結(jié)果。對于最近5分鐘的結(jié)果,每隔5s才更新一次。對于最近1小時的結(jié)果,每隔1分鐘更新一次。對于最近24小時的結(jié)果,每隔10分鐘才更新一次。用戶需要看結(jié)果的時候,永遠看的是 Cache 里的結(jié)果。另外用一個進程按照上面的更新頻率去逐漸更新Cache。

總結(jié):算法方面使用LFU算法來解決。而工程方面使用分段統(tǒng)計,抽樣法和Cache緩存進行優(yōu)化。

以上方案參考自:https://www.jiuzhang.com/qa/219/

4. 總結(jié)

  1. 網(wǎng)絡(luò)和操作系統(tǒng)需要復習,復習一些常見的網(wǎng)上都能搜到的字節(jié)考題就行,不用太深入。

  2. 算法 leetcode 刷 200~300 道差不多了,另外面試前搜一下字節(jié)考過的算法題,很有可能遇到相同的。

  3. 系統(tǒng)設(shè)計題就看平時積累,有想法就說出來,即使不是最優(yōu)解也要說出來。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容