首頁>技術>

你猜他通過了嗎

一面2020/12/20/14:00-14:50

1、自我介紹(2分鐘)

2、你說你讀過 redis 原始碼,那說一下常用的資料結構。

字串、跳錶、字典、壓縮列表、連結串列

3、介紹一下跳錶:

它在初始化的時候會分配一個頭,它也是和連結串列一樣鏈起來一個表嘛,它會給每個節點隨機分配一個層,然後它從它的頭部給每個節點進行每一層的連線,它的層數越高,它就會直接連到後面去,中間低的層就不連了,包括它的遍歷也是一樣,從高到低來找哪個數,這樣它就可以跳著遍歷了。

4、查詢的時間複雜度是多少:log(n)

5、空間佔用呢,相對於連結串列多了什麼:主要是多出了那個層嘛,它每個節點的層數是不一樣的,如果層都是1,也就退化成連結串列了。

6、vector push_back 擴容:2倍擴容,實際上可能更復雜一點

7、擴容以後,它的記憶體地址會變化嘛:會變化。以前的話,它是會把原來的元素重新複製過去,開闢一個新空間,複製過去,再把它之前的空間 free 掉。但是現在的話,是透過移動語義 move 來的

8、說一說 move 語義:相對於以前的複製來說,是把元素複製一遍,再把之前的銷燬掉。現在的 move 相當於是把它直接搬走了。也就是,我再想一種說法,也就是延長了它的生命週期,然後把新的指標直到原先的那個位置去,然後把原先的指標賦為空,差不多這樣。

9、move 底層的實現:emm,我看到了有 static_cast ,但是具體的,沒有完全瞭解過

10、右值引用:把那種臨時的變數,或者說馬上就要消亡,那個叫將亡值,把它的生命週期給延長,變成左值。

11、什麼時候被釋放:我的理解是它相當於一個左值了(意思和左值一樣)

12、怎麼把 vector 裡面存資料的所有記憶體空間都釋放掉:先說的 resize ,後面改說是另一個函式,不記得名字了

13、說一下虛擬函式:子類對父類的函式進行一個重寫。

14、舉個例子詳細說一下吧:舉例說了動物(父類)吃東西,其他子類吃的動作不一樣。這個時候對吃這個函式使用虛擬函式。

15、虛擬函式和函式重寫有什麼區別呢:(亂七八糟猜了一同)我這樣猜的

16、再具體說說:但是我不知道這樣猜的對不對

17、那換個問題吧,說說引用和指標:1. 記憶體佔用;2. 初始化;3. 可修改指向

18、還有嘛:沒了

19、引用可以銷燬嘛:不知道了

20、智慧指標瞭解過嘛,說說怎麼實現的:增加了一個引用計數

21、具體說說:不同的智慧指標指向同一個物件,有一個指標指向,它的引用計數就+1,最後變為0的時候,它就自動的銷燬了。

22、多執行緒中可以使用智慧指標嘛:不同執行緒裡面的智慧指標都是指向同一個物件嘛?是的:我覺得應該是適用的吧

23、你不知道是吧,確實是適用的,你是怎麼想的呢:它不同的執行緒是共享資源的吧,所以它們是可以指到同一個物件上的吧

24、會有讀寫衝突嘛:會有的

25、怎麼解決:應該透過加鎖來解決

手撕1. 中序遍歷(迭代)
給定二叉樹的,構造一箇中序遍歷的迭代器,對應的功能是返回二叉樹上的下一個元素。class Iterator {public:    TreeNode* next();}
1.1 思路討論(6分鐘左右)

我:在實現的過程中,需要把中序遍歷給記錄下來嘛面:你具體說說我:比如說,我在初始化的時候直接把它的中序遍歷存在 vector 中,直接用 next 這樣來訪問面:說說有什麼優點和缺點我:優點就是在呼叫 next 的時候複雜度是 O(1),缺點就是在初始化的時候要把它完全遍歷一遍,空間複雜度是O(n)面:你可以試試其他方法我:是不用額外的空間嘛面:也不是不用,但是比 O(n) 小點,比如 O(logn) 就行了。我:哦哦,那就是用一個棧,記錄它balabala....面:那你開始寫吧面:你先定義一個TreeNode我:需要定義Tree嘛面:這個不用我:這個需要執行嘛面:先不需要

1.2中途程式碼編寫(八分鐘左右)

面:根節點從建構函式傳入

#include <iostream>#include <stack>using namespace std; struct TreeNode {    int val;    TreeNode* left, *right;}; class Iterator {public:    stack<TreeNode*> s;     Iterator(TreeNode* root) {        // s.push(root);        TreeNode* t = root;        while (t) {            s.push(t);            t = root->left;        }    }    TreeNode* next() {        if (s.empty()) return nullptr;      // 面試官後面提醒        TreeNode* ret = s.top();        TreeNode* t = ret;        if (t&&t->right) {            t = t->right;            while (t) {                s.push(t);                t = t->left;            }        } else {            TreeNode* tRight = t;            s.pop();            if (s.empty()) return ret;        // 面試官後面提醒            t = s.top();            while (t->right == tRight) {                s.pop();                if (s.empty()) return ret;    // 面試官後面提醒                tRight = t;                t = s.top();            }        }        return ret;    }}
1.3 程式碼解釋(4分鐘)

寫完之後,讓我解釋程式碼,並指出幾個沒有加特殊情況的位置。

2. 全排列
給定一個數組,列印它的全排列[1,2]->  [1,2] [2,1]
2.1 說思路(2分鐘)面:說不太清,你覺得沒問題就直接寫吧2.2 程式碼(7分鐘)
#include <iostream>#include <vector>using namespace std; vector<vector<int>> ans; void sol(vector<int>& v, vector<int> ret, vector<bool> flag) {    if (ret.size() == v.size()) ans.push_back(ret);    for (int i = 0; i < v.size(); ++ i) {        if (flag[i]) {            ret.push_back(v[i]);            flag[i] = false;            sol(v, ret, flag);            flag[i] = true;            ret.pop_back();        }    }} int main() {    vector<int> v;    for (int i = 1; i <= 3; ++ i)        v.push_back(i);    vector<int> ret;    vector<bool> flag(v.size(), true);    sol(v, ret, flag);    for (int i = 0; i < ans.size(); ++ i) {        for (int j = 0; j < ans[i].size(); ++ j)            cout << ans[i][j] << ' ';        cout << endl;    }}
反問我:你覺得我還有什麼需要加強的地方嘛面:你的廣度還行,但是深度不夠,比如虛擬函式和智慧指標二面2020/12/21/14:05-15:05

自我介紹

簡歷有寫c++,接受其他語言嘛:具體什麼語言

golang,python:可以接受,只是偏向c++

實習時間:沒有其他問題,可以實習到轉正

說明轉正時間(提醒我時間很長):沒特殊情況,希望實習到轉正

base上海?:是的

闡述實習的工作(3分鐘)

說說程序、執行緒、協程:說了程序是資源排程單位,執行緒是cpu排程單位,協程有個通道可以傳輸資料。粒度上:程序 > 執行緒 > 協程

可以試著說說它們的優缺點:程序切換的缺點,時間開銷;其他的我可能不太瞭解,就說這麼多吧

說說程序、執行緒的通訊:程序:通道(口誤把管道說成了通道。。)、訊號,socket也算吧;執行緒用訊號比較多吧。

演算法題

說一說lru,快取大小為K,怎麼設計資料結構:用連結串列儲存快取資料,說了訪問的過程;繼續說連結串列查詢上的缺點,時間複雜度為O(n),所以用雜湊來查詢,時間複雜度可以降為O(1)。(2分鐘)

如果加上過期時間呢:在連結串列節點中加一個更新時間,在訪問資料的時候,判斷一下時間有沒有過期,就行了吧。(3分鐘)

寫一下吧:

(剛開始的時候自己實現的 list 來寫的,挺麻煩的,我突然反應過來之前是用stl的。這時候已經過了15分鐘)我:我可以改成用stl麼。。。。

面:可以的,你改

(15分鐘後,寫完了)

#include <iostream>#include <unordered_map>#include <list>#include <pair>using namespace std; struct ListNode {    int val;    ListNode* next, *pri;    double update;}; class lru {    // ListNode* head, *tail;    list<pair<int, double>> l;    unordered_map<int, list<pair<int, double>>::iterator > m;    int K;    int nums;    double T;    lru(int k, double t) {        K = k;        nums = 0;        T = t;    }    void set(int x) {        if (m.find(x) == m.end()) {            auto p = make_pair(x, time());            l.push_back(p);            m[x] = l.rbegin();            nums ++;        } else {            auto p = m[x];            p->first = x;            p->second = time();            l.erase(p);            l.push_back(*p);            m[x] = l.rbegin();        }        if (nums > K) {            l.pop_front();            nums --;        }    }     int get(int x) {        if (m.find(x) == m.end()) {            return 0;        } else {            auto p = m[x];            if (time() - p->second >= T) {                m.erase(x);                l.erase(p);                return 0;            } else {                p->second = time();                l.erase(p);                l.push_back(*p);                m[x] = l.rbegin();                return x;            }        }    }}

如果是多執行緒裡面會出錯嘛:會出錯

那怎麼解決呢:在對資料結構內部進行修改的時候加鎖

具體點:

// 這裡加寫鎖l.erase(p);l.push_back(*p);m[x] = l.rbegin();

mysql 的索引資料結構:B+樹

說說為什麼用 B+ 樹:說了範圍查詢的優點

說說 git merge 和 git rebase 哪個好:rebase

說說有什麼優點:不太清楚

反問

看原始碼和做專案,從面試和自我提升的角度哪個更好:都很重要。後者重要一點

面的哪個組:廣告創意

面試官介紹了4分鐘:感覺還挺有意思的

三面2020/12/24/14:00-14:55

自我介紹(2分鐘)

實習介紹(12分鐘)

redis常用資料結構:字串、字典、連結串列、跳錶、壓縮列表、集合、有序集合

有序集合的實現方式:跳錶和壓縮列表

具體說說:壓縮列表用於資料量小的情況,儲存方式是線性的;資料量大的時候會自動轉換為跳錶,跳錶和連結串列一樣,由一個一個節點鏈起來,但是它的節點和連結串列不一樣,它的節點會被隨機分配一個層數,相同的層數連結起來。

跳錶查詢的時間複雜度:log(n)

有序集合中有查詢時間複雜度為O(1)的實現嗎:有序集合應該是沒有的。

redis 的過期策略:有好幾種吧,常見的lru

之前用的資料庫是什麼:mysql

引擎是什麼:innodb

隔離級別是:可重複讀

怎麼實現的可重複讀:MVCC

使用的樂觀鎖還是悲觀鎖:樂觀鎖

MVCC的實現:增加了版本號,有個版本鏈,每次事務開始的時候有一個快照,其他事務讀取的時候,讀取那個快照。事務內部修改的時候有一個日誌,日誌會記錄事務內部對這一行的修改,會用於事務的回滾。

binlog、redolog(也可能是undo log)聽說過嗎:聽說過,但是沒深入瞭解過

innodb的索引資料結構是什麼:B+樹

主鍵和非主鍵的索引呢:它一開始只會對主鍵建索引吧

非主鍵呢:不清楚

B+樹的葉子節點存放的什麼:整體的資料,包括索引,都在裡面

瀏覽器輸入一個url,返回了一個error,怎麼找出錯的地方:透過狀態碼

沒有狀態碼,只有一個error:啥都沒有嗎。。。

dns的返回標誌:那可能是dns上的錯誤,可能不存在這個域名

但是我輸入的toutiao.com,我知道是存在的:是的。那可能是dns查詢的時候出現了錯誤,那可以一步一步的查,它剛出去的時候訪問的那個dns伺服器(應該都是有的,小聲bb)。可能最近的那個路由器或者電腦裡面沒有配dns。有沒有可能(強顏歡笑)

你怎麼知道最近的呢:查詢dns的路徑,好像是有一個命令的

什麼命令呢:這命令名字我不記得了。。。

linux怎麼檢視系統的狀態呢:top

top出來顯示什麼:cpu佔用率,記憶體佔用率、一些程序的資訊

實習中,python後端,是怎麼實現的http連線,是django嗎:emm,是透過webpy的api直接呼叫的。。。。

你的request,post,具體實現是什麼:emmm,我可以從c++方面來說嗎

嗯:它要建立一個套接字,給套接字分配一個埠,等待連線的到來,連線來了之後,像redis的話,它收到連線,會複製一個socket,分配一個新埠,把這個連線分配給新socket,原先的socket繼續listen。連線之後,讀取socket裡面緩衝區的內容,再內部進行處理,再透過socket傳輸出去。

socket監聽是哪個層:tcp

之前問的是http,這方面是不是不太瞭解:嗯

還有什麼領域沒問你的嗎:作業系統,計算機網路,你們應該用golang,python比較多吧

也有用c++的:那還有c++這些,瞭解的多一些

段頁式儲存執行的時候會訪問幾次記憶體:訪問記憶體會透過頁的級數也有關係,如果它是一級頁表的話,它取頁號一次,取物理地址一次,如果沒有在記憶體中的話,還會訪問磁碟

我問的段頁式:段頁式先取它的段內偏移量,然後再訪問物理地址,是兩次

程序的儲存分佈:棧、堆、資料區、程式碼區、共享庫

程式碼放在哪:程式碼區

變數放在哪:靜態變數放在靜態的變數區,區域性變數會放在棧中

指標呢:指標也是有靜態的吧(小聲(不確定)),直接宣告的會放在棧中

指標指向的地址分配的空間呢,malloc的:放在堆中

演算法

做題吧,我先看看你之前做的什麼題

求s1中包含s2中字元(不用考慮順序)的最小子串(長度最小)s1 = "abcedeabd"s2 = "abe"
思路

滑動視窗:

可以先找到第一個包含s2字元的字串,比如例子中的abce,然後往右移,把a提出去,找到右邊第一個提出去的a,這就是下一個子串。把所有子串找出來,再找最短的。

面:你這個會有問題,你先寫程式碼吧。

#include <iostream>#include <unordered_map>#include <unordered_set>#include <vector>#include <string>using namespace std;int main() {    unordered_map<char, int> um1, um2;    unordered_set<char> us;    string s1 = "abcedeabd";    string s2 = "abe";    vector<string> ans;    int left, right;    left = right = 0;    int sum = 0;    for (int i = 0; i < s2.length(); ++ i) {        us.insert(s2[i]);        um1[s2[i]] ++;        sum ++;    }    string s;    um2 = um1;    int i = 0;    while (i < s1.length() && um2[s1[i]] == 0)        i ++;    while (sum > 0) {        if (um2[s1[i]]) {            um2[s1[i]] --;            sum --;        }        s += s1[i];        i ++;    }    ans.push_back(s);    for (; i < s1.length(); ++ i) {        char ch = s[0];        int cnt = 1;        // 找到擷取的地方        if (um1[s[0]] == 1) {            while (us.find(s[cnt]) == us.end()) {                // TODO            }        }        while (s[cnt] )        s = s.strsub(1, s.length() -1);        while ()        if (sum == 0){            ans.push_back(s);            um2 = um1;        }    }    cout << "Hello World!" << endl;}

(17分鐘後)

面:沒什麼時間了,說說程式碼吧

我:(對著程式碼)先找到第一個符合條件的子串,然後移除掉第一個字元s[0],這裡需要把s中,非s2字元的其他字元也移除掉,這裡就有問題了,如果後面的s[cnt](已經是s[cnt] in s2了),正好是移除掉的s[0],這裡有兩種情況,如果s2中只有一個s[0],那麼這個時候right不用右移,如果s2有多個s[0],則與其他字元做同樣的處理。感覺後面還有挺多要寫的。。。

面:還有更好的解法,你可以之後去想一想。

反問

問:推薦一本書

面:這種主觀的推薦我們是規定不能說的哈

問:上下班時間

面:10105.5,你們應該都知道

我:不同組也有差異吧哈哈

面:那你想要多呢還是少呢(笑)

三面沒過,換了個組繼續加面兩輪

20
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • SAP許可權管理技術架構介紹