從哪裡跌倒,就從哪裡站起來。

我一直信奉著這句話,於是我又去群暉科技 (Synology) 面試了。這間公司是我去年最想去的公司之一,也是我去年面試的第一間公司,更是給我一記當頭棒喝的公司。

結果今年,我還是挑戰失敗了。

很巧的是或者可以說很衰的是,今年竟然還是遇到去年把我掃地出門的那位主管。而我一樣在他那關我就被請出公司了,雖然說三天後會告知結果,不過相信大家都知道群暉是當天給 offer 的。基本上沒有當天拿到 offer 就是能力不受到認可了,而且我想他三天後也不會告知我結果,去年也是這樣直接不了了之。

個人認為群暉的面試真的不輕鬆,以白板程式題來說,實現解法後,面試官會持續的要求你改進這個演算法。例如:減少記憶體使用量、降低時間複雜度等等…。對 coding 能力的要求還滿高的,但就這次的白板題的表現來說,如果滿分是 10 分,我會給自己 7.5 分。畢竟還是有些小 bug 的存在被點了出來。但我想白板程式題應該沒有太大的問題,沒有跟面試官頂撞,而且都算有完成。

那最大的問題,應該就是技術問答上了。第二階段的面試官非常嚴格,基本上履歷上有寫什麼他就問什麼。有扯到一點資料庫的經驗,他就問你資料庫的問題,有扯到嵌入式他就問你嵌入式的問題,寫到網路管理他就問你網路的問題。每個問題都不是像我這種什麼都學一點的庸才回答的出來的。此外,畢竟都不是一直在做那些事情,很多東西說實在真的忘記了。

這讓我想起之前去 Jserv 學長的課程分享時,他曾指著我的履歷說:

各位看這位同學的履歷,看起來相當平庸。好像什麼都會一點,但卻不知道他到底會什麼。

這次的面試,讓我徹底理解了這句話。以前,我都認為我應該要接觸的廣,要什麼都會一點。當必須要專精時,再深入研究就好。而這成為我最大的弱點,因為每個學問我都只學了皮毛,但面試官一問的深入一點,我就掛彩了。曾經想當個通才,但我現在理解到我連個專才都不是,憑什麼當個通才?

雖然有人說群暉很看血統,但我認為如果是個真的有能力的人來面試,不管什麼學校畢業,群暉都會錄用。所以,我還是比較傾向於是我自己能力不足。

除了能力不足外,就是沒有調整好面試的策略。最大的敗筆就是履歷了,我應該著墨在我專長的部分。而不是寫上一些曾經做過什麼皮毛,寫上一寫只是有過經驗的項目。應該要引導面試官詢問你自己真的專精的項目,畢竟一直回答不出來看起來就很遜!!

但也就是因為能力不足,我才只好寫上那些只會皮毛的東西。不然我只能寫演算法跟資料結構,跟程式競賽的成績了。自己好像真的沒有什麼比較專精的項目,就連自己在做的研究我也不敢說我真的專精。

所以,一言以蔽之就是:我能力還太淺了!


題外話:

不過還是想抱怨一下那位面試官,他一直給我一種很瞧不起人的感覺,一直用這也沒聽過的語句來挑戰你。不過我個人覺得比較機車的是他對於我寫的程式碼都抱著一種懷疑輕挑的態度。像是去年的程式我要交換 linked list 的節點,面試官規定不可以交換節點的內容,只能改變連結,我寫了個類似這樣的語法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
#include <algorithm>
using namespace std;

struct Node {
    int val;
    Node* next;
    Node(int x = 0, Node* p = NULL)
        : val(x), next(p) {}
};

int main() {

    Node *head;

    // 建構 linked list
    // bla bla bla...

    // 從中取出欲交換的兩點
    Node *p1 = linked list 中的某一點;
    Node *pre1 = p1 的前一個點;
    Node *p2 = linked list 中的某一點;
    Node *pre2 = p2 的前一個點;

    // 交換 p1 與 p2 的位置
    if (p1 != p2) {

        // 父節點為 head 時替換掉 head
        if (p1 == head) {
            head = p2;
            pre2->next = p1;
        }
        else if (p2 == head) {
            head = p1;
            pre1->next = p2;
        }
        else {
            // 交換父節點的子節點
            swap(pre1->next, pre2->next);
        }

        // 交換子結點
        swap(p1->next, p2->next);
    }

    return 0;
}

基本上,STL 提供的 swap 本身就是以傳參考的方式來傳遞參數,因此是可以有效交換 p1 所指向的節點與 p2 所指向的節點。以上面的程式碼來說,原本的 linked list 是由 p1 開始,並指向 p2。但經過交換後,會變成 p2 開始,並指向 p1。以下是 STL 的 swap 實作:

1
2
3
template <class T> void swap ( T& a, T& b ) {
    T c(a); a=b; b=c;
}

但他一直針對這個 swap 攻擊,還一直笑說「怎麼可能這樣就可以換,你確定?!」為了這一點,我們爭論 STL 的 swap 是否為 call by reference 很久。最後我說我自己寫一個 swap 好了,他卻又說算了…。

所以今年,如果我要交換我絕對不會用 swap 的…畢竟遇到同一個嘛!今年我寫了一段程式是需要傳遞指標作為參數,但這個參數如果在函式內被更動時,那麼函式外的實體也要被更動。所以我的函式原型就這樣寫:

1
2
3
4
void foo(Node* &p) {
    // bla bla bla...
    p = x;
}

於是他又開始笑…說「你到底在寫 C 還是 C++ 啊?C++ 可以這樣寫嗎?怎麼會有這種寫法?有人在參考前面加星號的嗎?」於是,我就解釋這樣的寫法表示這是參考到一個指標的型別。不過解釋完,我馬上說如果不用這樣的方式,我會改寫函式的原型讓他回傳一個指標。也就是不管該指標有沒有被更動,我都會將它現在的值傳回來,程式碼如下:

1
2
3
4
5
Node* foo(Node* p) {
    // bla bla bla..
    p = x;
    return p;
}

所以今年沒有跟他爭辯,解釋完我的後,就馬上用另一種他可以接受的寫法給他。不知道是不是故意要這樣質疑我的程式碼來看我的反應,還是就是純粹不相信我…因為就算我解釋完了他還是有點不屑的態度,就隨便敷衍一下結束這個話題。

編修紀錄

  • 2016/04/13: 感謝網友指正,在交換兩個節點時,其實不用特別判斷相鄰的狀況。