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道題目。總之任重而道遠。