字節跳動二次筆試總結

1. 前言:    

此次筆試是少數幾回我本身獨立完成的,可是結果並很差寫了兩道題目。起初咱們實驗室三我的,商量着一人作一道,由於兩個小時作4道題目,對咱們這些菜雞來講幾乎是不可能完成的任務。但結果是,都tmd快考完了,我負責的最後一題還沒寫出來。最終個人第一道題目是在同門的協助下寫出來的,第四道題目趕快結束才寫完,直接還沒來得及運行用例就提交了。實際上最後一題個人思路是比較清晰的,可是在建立圖和寫DFS的時候花了太多的時間,還有就是寫代碼的時候,必定要思惟先行,想清楚要幹什麼再寫。下面我來帶你們看看這四道題目:

2. First Problem

第一題叫作「豆油瓶」,題目相似於Leetcode 547 朋友圈。

2.1 題目描述:

抖音上天天有幾億用戶,若是用戶A和用戶B互動很多於三次,咱們就認爲A和B屬於豆油,若是A和B屬於豆油,B和C屬於豆油,那麼A和C 也屬於豆油。咱們定義豆油瓶就是由直系和間接朋友所組成的羣體。
 
給定一個N*N的矩陣M,表明抖音上全部用戶的互動次數,若是M[i][j] = 5,那麼第i個用戶和第j個用戶的互動次數就爲5次,爲0的話表明沒有互動。對於i == j,即同一個用戶,互動次數咱們計爲0。請你計算並輸出發現的抖音上的豆油瓶的個數。
   
樣例輸入:
    3
    0 4 0
    4 0 0 
    0 0 0
樣例輸出:2 (用戶0和用戶1的互動次數爲4次,因此0和1組成了一個豆油瓶,用戶2沒有產生互動,因此單獨爲一個豆油瓶)

2.2. 解答:

這道題實質上就是求無向圖的連通份量個數,因此咱們用DFS來解答。不過也能用並查集來作。
思路是:1. 根據約束條件建立無向圖 2. 求連通份量個數
var marked []bool
var count int

func DFS(v int, g [][]int) {
    fmt.Println("v", v, "marked", marked)
    marked[v] = true
    adjs := getAdjs(v, g)
 
    for _, adj := range adjs {
        if marked[adj] == false {
            DFS(adj, g)
        }
    }
}

func getAdjs(v int, g [][]int) []int {
    var adjs []int
    for i, elm := range g[v] {
        if elm != 0 {
            adjs = append(adjs, i)
        }
    }
    return adjs
}

func createGraph(comm [][]int, n int) [][]int {
    for i := 0; i < n; i++ {
        for j := 0; j < n; j++ {
            if comm[i][j] >= 3 {
                comm[i][j] = 1
            } else {
                comm[i][j] = 0
            }
        }
    }
    return comm
}

func solution(comm [][]int, n int) {
    g := createGraph(comm, n)
    fmt.Println(g)
    marked = make([]bool, n)
    count = 0

    for i := 0; i < n; i++ {
        if marked[i] == false {
            DFS(i, g)
            count++
        }
    }
}

3. Second Problem

第三題對我來講是這幾道題目中最難理解的一道題目。下面咱們來看看吧。
 
 
 

3.1 題目描述

現有一個圓形花園共有n個入口,如今要修一些路,穿過這個花園。要求每一個路口只能有一條路,全部的路均不會相交,求全部可行的方法總數。輸入輸出如上圖所示。

3.2 解答

這道題目,我真的是光徹底理解題目就花了很久,側面反映出個人智商有問題,還須要好好磨練。這道題是使用的是動態規劃的思路,下面我來解答如下這道題目的思路。
 
咱們先給每一個入口從1開始編號,先假設6個入口的狀況。對於1號口,能夠鏈接的入口有 2,3,6號。
 
若是鏈接1,2,6個點的問題會被分解爲2個點和4個點的問題。爲何?看下面的圖,是否是很清晰。因此這種狀況下的方法總數是F(2) *  F(4)。爲何是乘法,由於這裏不是獨立的關係,是先定兩個點,在這個基礎上在計算4個點的狀況。

若是鏈接1,3,子問題的劃分和上述討論是相似的。所以總數也等於F(2) * F(4)算法

可是鏈接1,6的話,狀況就不一樣了。這裏1,6間的路把整個花園分紅了兩個部分,每一個部分只有兩個口,所以方法總數是F(2) * F(2)數據結構

最後六個點的狀況就等於以上幾種狀況相加,所以F(6) = F(2)*F(4) + F(2)*F(4) + F(2)*F(2) = 1*2 + 1*2 + 1 = 5
按說應該在多討論幾種狀況,才能得出遞推公式,可是時間有限,這裏我就直接給出來:
對於第二項,F(i)*F(j),i+j = n-2。
//f[2m] = 2*f[2m-2]*f[2] + f[c1]*f[c2], c1+c2 = 2m-2
func solution(n int) int {
    m := make([]int, n+1)
    m[0] = 0
    m[2] = 1
    for i := 4; i <= n; i = i + 2 {
        tmp := 0
        for j := 2; j <= i-2; j = j + 2 {
            tmp += m[i-2-j] * m[j]
        }
        m[i] = 2*m[i-2]*m[2] + tmp
    }
    return m[n]
}

4. Third Problem

4.1 題目描述

 

4.2 解答

這道題應該是這四道題目,最簡單的一道了,沒有任何特殊的技巧。關鍵是理解題目的意思,和咱們一般玩的2048不一樣,這裏進行一次操做,整個矩陣都會朝目標方向移動,而且相鄰會碰撞的兩個數字會合並,而且兩個數字只會觸發一次合併,且優先合併移動方向頂端的位置。
 
對於行[ 2 2 2 2 ],向右移動後,該行變爲,[ 0 0 4 4 ]。那麼是如何變成這樣的呢?
 
先進行合併操做,a[3] = a[3] + a[2],a[1] = a[1] + a[0]。以後行變爲[ 0 4 0 4]。以後進行右移操做,即a[2] = a[1], a[1] = 0。 右移以後,行變爲[ 0 0 4 4 ]。
func Up(board [][]int) {
    for i := 0; i < 4; i++ {
        board[0][i] += board[1][i]
        board[2][i] += board[3][i]
        board[1][i] = board[2][i]
        board[2][i] = 0
        board[3][i] = 0
    }
}

func Down(board [][]int) {
    for i := 0; i < 4; i++ {
        //
        board[1][i] += board[0][i]
        board[3][i] += board[2][i]
        //
        board[2][i] = board[1][i]
        board[0][i] = 0
        board[1][i] = 0
    }
}

func Left(board [][]int) {
    for i := 0; i < 4; i++ {
        board[i][0] += board[i][1]
        board[i][2] += board[i][3]

        board[i][1] = board[i][2]
        board[i][2] = 0
        board[i][3] = 0
    }
}

func Right(board [][]int) {
    for i := 0; i < 4; i++ {
        board[i][3] += board[i][2]
        board[i][1] += board[i][0]

        board[i][2] = board[i][1]
        board[i][1] = 0
        board[i][0] = 0
    }
}

func solution(oper []int, board [][]int) [][]int {
    for i := 0; i < len(oper); i++ {
        switch oper[i] {
        case 1:
            Up(board)
        case 2:
            Down(board)
        case 3:
            Left(board)
        case 4:
            Right(board)
        }
    }

    return board
}

5. Forth Problem

5.1 問題描述

 
 
在漫天的星空裏散落着一些糖果,他們各有各的甜度。有一些糖果之間會按照必定的規則有橋樑鏈接,好讓你得到了這個糖果以後,能夠去得到和該糖果相連的其餘糖果。如今讓你從一個糖果出發,去儘量多的獲取糖果。
咱們將糖果編號 1 ... ... n。每一個糖果的甜度記爲a[i]。若糖果i和j的甜度的最大公約數>1。則糖果i和j之間有橋樑鏈接。

5.2 解答

這道題也是使用DFS進行求解,每一個糖果是圖的一個節點。知足約束條件的結點間有邊相連。這道題實質上是求擁有最多節點的連通子圖的節點個數。所以把第一個問題的代碼修改如下就OK了。關鍵是設置一個變量max去存儲最大值,和變量c1去存儲當前連通子圖的節點個數。
func createGraph(sweets []int) [][]int {
    n := len(sweets)
    graph := make([][]int, n)
    for i := 0; i < n; i++ {
        graph[i] = make([]int, n)
        for j := 0; j < n; j++ {
            graph[i][j] = 0
        }
    }

    for i := 0; i < n; i++ {
        for j := i + 1; j < n; j++ {
            if GCD(sweets[i], sweets[j]) > 1 {
                graph[i][j] = 1
                graph[j][i] = 1
            }
        }
    }
    return graph
}

var marked []bool
var count, max int

//gcd(x, y) = gcd(y, x%y)
func GCD(x, y int) int {
    for y != 0 {
        r := y
        y = x % y
        x = r
    }
    return x
}

func DFS(v int, g [][]int) {
    count++
    marked[v] = true
    adjs := getAdjs(v, g)

    for _, adj := range adjs {
        if marked[adj] == false {
            DFS(adj, g)
        }
    }
}

func getAdjs(v int, g [][]int) []int {
    var adjs []int
    for i, elm := range g[v] {
        if elm != 0 {
            adjs = append(adjs, i)
        }
    }
    return adjs
}

func solution(n int, sweets []int) {
    graph := createGraph(sweets)
    marked = make([]bool, len(sweets))
    for i := 0; i < n; i++ {
        if marked[i] == false {
            count = 0
            DFS(i, graph)
            if count > max {
                max = count
            }
        }
    }
}

6. 總結

此次筆試的慘敗,反映出我數據結構和算法的薄弱(要達到對於任何數據結構的代碼都爛熟於心的程度)。其次,是秋招準備的不充分。最後是智商的侷限。牛客上不少人都AC了2道或者3道題目。總之任重而道遠。
相關文章
相關標籤/搜索