模擬筆試記錄 - 快手2020校園招聘秋招筆試 - 工程C試卷


選擇題

1、關於java的異常處理機制,以下說法正確的是:

當某個線程拋出OutOfMemoryError時,其他線程有可能不受影響
當大量拋出RuntimeException時,不會影響系統的吞吐量
java.lang.Exception是java.lang.Error的父類
finally塊代碼一定會被執行

finally塊代碼並不一定會執行,不是很懂java的try、catch、finally。

2、錯誤原因:512+256+128+...+1=1023,不要算錯

3、關於mysql,下面說法不正確的是:

mysql中的utf8類型最大只支持3個bytes
desc關鍵字可以作為table的字段名
filesort是通過讀取磁盤文件進行排序,會極大降低查詢性能
smallint占用2個bytes的存儲空間

utf8類型最大只支持3個bytes,utf8mb4類型最大只支持4個bytes。
filesort並非是對磁盤文件進行排序。好像是先對每個緩沖塊進行快速排序,然后對各個塊進行歸並排序。

5、查找表結構用以下哪一項?

DESC

mysql中,查詢表結構用DESC,可以顯示各列名以及各列的限制(是否主碼,是否非空)。

6、如何強制垃圾回收器立即回收一個對象?

調用System.gc()方法
調用Runtime.gc()方法
將對象賦值null
無法強制垃圾回收器立即執行

System.gc()只是提醒垃圾回收器執行,不是強制其執行。

7、關於sleep()和wait(),以下描述錯誤的一項是?

sleep是Thread類的方法,wait是Object類的方法;
sleep不釋放對象鎖,wait放棄對象鎖;
sleep暫停線程、但監控狀態仍然保持,結束后會自動恢復;
wait后進入等待鎖定池,只有針對此對象發出notify方法后獲得對象鎖進入運行狀態。

notifyAll方法也可以。

參考資料:sleep()和wait()的區別及wait方法的一點注意事項 - shiki0921 - 博客園

sleep是Thread類的方法,導致此線程暫停執行指定時間,給其他線程執行機會,但是依然保持着監控狀態,過了指定時間會自動恢復,調用sleep方法不會釋放鎖對象。

當調用sleep方法后,當前線程進入阻塞狀態。目的是讓出CPU給其他線程運行的機會。但是由於sleep方法不會釋放鎖對象,所以在一個同步代碼塊中調用這個方法后,線程雖然休眠了,但其他線程無法訪問它的鎖對象。這是因為sleep方法擁有CPU的執行權,它可以自動醒來無需喚醒。而當sleep結束指定休眠時間后,這個線程不一定立即執行,因為此時其他線程可能正在運行。

wait方法是Object類里的方法,當一個線程執行到wait方法時,它就進入到一個和該對象相關的等待池中,同時釋放了鎖對象,等待期間可以調用里面的同步方法,其他線程可以訪問,等待時不擁有CPU的執行權,否則其他線程無法獲取執行權。當一個線程執行了wait方法后,必須調用notify或者notifyAll方法才能喚醒,而且是隨機喚醒,若是被其他線程搶到了CPU執行權,該線程會繼續進入等待狀態。由於鎖對象可以是任意對象,所以wait方法必須定義在Object類中,因為Obeject類是所有類的基類。

8、下列協議中,將MAC地址轉為IP地址的協議是:

RARP

ARP(Address Resolution Protocol)地址解析協議,是根據IP地址獲取物理地址的一個TCP/IP協議。
RARP(Reverse Address Resolution Protocol)是反向地址解析協議。

10、在TCP/IP體系結構中,直接為ICMP提供服務的協議是:

IP

不過啥是ICMP呀。

11、下列關於進程和線程的敘述中,正確的是:

不管系統是否支持線程,進程都是資源分配的基本單位
線程是資源分配的基本單位,進程是調度的基本單位
系統級線程和用戶級線程的切換都需要內核的支持
同一進程中的各個線程擁有各自不同的地址空間

B選項:明顯線程是用來調度的,進程才是資源分配的。D選項:同一進程的線程貢獻這個進程的資源。

12、若某單處理器多進程系統中有多個就緒態進程,則下列關於處理機調度的敘述中,錯誤的是:

在進程結束時能進行處理機調度
創建新進程后能進行處理機調度
在進程處於臨界區時不能進行處理機調度
在系統調用完成並返回用戶態時能進行處理機調度

好像是這么理解:B選項:假如一開始沒有進程,那么創建新進程完成后就會進行處理機調度。C選項:有時,臨界區是類似進程訪問打印機之類的外設,在等待外設的過程中造成的。這個時候是可以進行處理機調度的。其他情況不清楚是否可以。

13、關於TCP協議的描述,以下錯誤的是?

面向連接
可提供多播服務
可靠交付
報文頭部長,傳輸開銷大

TCP是連接的、可靠的,UDP是無連接的,不可靠的。(TCP是一個虛擬連接,握手的過程就是為了建立虛擬連接,發送還需要對方進行確認,擁有擁塞控制。UDP是無連接的,只顧自己發送,一直盡力去做好,在一些需要實時性的傳輸中,UDP可以沒這么卡)
TCP僅支持單播,不能提供多播與廣播,每次是只面向連接的兩台設備的。
TCP加了很多東西,UDP只是簡單加了報文頭表示這個是UDP協議。

UDP TCP
是否連接 無連接 面向連接
是否可靠 不可靠傳輸,不使用流量控制和擁塞控制 可靠傳輸,使用流量控制和擁塞控制
連接對象個數 支持一對一,一對多,多對一和多對多交互通信 只能是一對一通信
傳輸方式 面向報文 面向字節流
首部開銷 首部開銷小,僅8字節 首部最小20字節,最大60字節
適用場景 適用於實時應用(IP電話、視頻會議、直播等) 適用於要求可靠傳輸的應用(文件傳輸)

參考資料:
TCP的可靠傳輸機制_網絡_sinat_28557957的博客-CSDN博客
TCP/IP詳解之:廣播和多播 - 墨城煙雨 - 博客園
怎么理解TCP的面向連接和UDP的無連接(不面向連接)?_網絡_infi-CSDN博客
主要看這個:一文搞懂TCP與UDP的區別 - Fundebug - 博客園

15、有關C++程序運行時的函數地址,下列說法正確的是:

每個函數的地址都是固定的,同一個程序重復運行多次,每次函數地址都一樣
每個函數的地址是不固定的,但在操作系統版本、硬件版本不變的情況下是固定的,同一個程序重復在相同軟硬件環境下運行多次,每次函數地址都一樣
地址是否固定要看系統配置和編譯選項,如果開啟了地址隨機化,那地址是每次都變的,如果沒開啟,那么地址每次都一樣
每個函數的地址都是不定的,同一個程序重復運行多次,每次地址都不同

好像地址隨機化是為了對抗反匯編的。默認是沒有的。

17、關於TCP協議狀態描述正確的是

只有執行主動關閉端才會出現TIME_WAIT
當接受到FIN報文時,會進入CLOSING狀態
數據傳輸完成后發送FIN報文后進入TIME_WAIT狀態
client和server端最終都會經歷TIME_WAIT狀態

參考上面的TCP的解除連接的四次揮手。

開始:雙方是建立連接狀態。

1、客戶端發送FIN,自己進入FIN_WAIT狀態。並停止自己發送新的數據。

2、服務端接收FIN,自己進入CLOSE_WAIT狀態,告訴應用層客戶端到服務端的連接已經停止,返回一個ACK。服務端若還有數據發送,客戶端依然需要接收。

3、客戶端接收ACK,然后還要等待服務端發送剩余的數據。

4、服務端發送數據完畢,發送FIN,自己進入LAST_ACK狀態,等待客戶端的ACK。

5、客戶端接收FIN,返回ACK,進入CLOSE_WAIT狀態,然后等待超時(2MSL,兩倍最長報文壽命),若沒有收到服務器繼續發送的FIN(意味着自己返回的ACK丟失了,要重發ACK),則關閉連接。

6、直到服務端收到ACK,關閉連接,否則持續發送FIN。

所以服務器關閉連接會早一點。

【問題1】為什么連接的時候是三次握手,關閉的時候卻是四次握手?

答:因為當Server端收到Client端的SYN連接請求報文后,可以直接發送SYN+ACK報文。其中ACK報文是用來應答的,SYN報文是用來同步的。但是關閉連接時,當Server端收到FIN報文時,很可能並不會立即關閉SOCKET,所以只能先回復一個ACK報文,告訴Client端,"你發的FIN報文我收到了"。只有等到我Server端所有的報文都發送完了,才能發送FIN報文,因此不能一起發送。故需要四次握手。

【問題2】為什么TIME_WAIT狀態需要經過2MSL(最大報文段生存時間)才能返回到CLOSE狀態?

答:雖然按道理,四個報文都發送完畢,可以直接進入CLOSE狀態了,但是必須假想網絡是不可靠的,有可能最后一個ACK丟失。所以TIME_WAIT狀態就是用來重發可能丟失的ACK報文。在Client發送出最后的ACK回復,但該ACK可能丟失。Server如果沒有收到ACK,將不斷重復發送FIN片段。所以Client不能立即關閉,它必須確認Server接收到了該ACK。Client會在發送出ACK之后進入到TIME_WAIT狀態。Client會設置一個計時器,等待2MSL的時間。如果在該時間內再次收到FIN,那么Client會重發ACK並再次等待2MSL。所謂的2MSL是兩倍的MSL(Maximum Segment Lifetime)。MSL指一個片段在網絡中最大的存活時間,2MSL就是一個發送和一個回復所需的最大時間。如果直到2MSL,Client都沒有再次收到FIN,那么Client推斷ACK已經被成功接收,則結束TCP連接。

參考資料:(5條消息)TCP的三次握手與四次揮手理解及面試題(很全面)_網絡_lucky_jun-CSDN博客

18、關於epoll和select的區別,哪個說法是錯誤的?

epoll和select都是I/O多路復用的技術,都可以實現同時監聽多個I/O事件的狀態。
epoll相比select效率更高,主要是基於其操作系統支持的I/O事件通知機制,而select是基於輪詢機制。
epoll支持水平觸發和邊沿觸發兩種模式。
select能並行支持I/O比較小,且無法修改。

完全不懂這是什么。

編程題

21、尺取/二分

小明最近在做病毒自動檢測,他發現,在某些library 的代碼段的二進制表示中,如果包含子串並且恰好有k個1,就有可能有潛在的病毒。library的二進制表示可能很大,並且子串可能很多,人工分析不可能,於是他想寫個程序來先算算到底有多少個子串滿足條件。如果子串內容相同,但是開始或者結束位置不一樣,則被認為是不同的子串。

注:子串一定是連續的。例如"010"有6個子串,分別是 "0, "1", "0", "01", "10", "010"。

數據范圍:1<=n<=1e6, 0<=k<=1e6

題解:很明顯的一個尺取的題目,尺取的步驟是用l和r框住恰好k個1,然后數向左向右的全0分別有多少個。需要注意的是k=0的時候要另外處理。搞得好像很多細節的樣子,早知道直接二分就可以了,枚舉每個左端點,然后在前綴和上面二分找出差分為k的一段,這一段都是可行的。或者對連續的0進行壓縮,對於每個1,存放其右側的連續的0的數量。然后特殊處理一下第一個1的左側,每次就是k個1直接尺取,不需要太多情況。

char s[1000005];
 
void test_case() {
    int n, k;
    scanf("%d%s", &k, s + 1);
    n = strlen(s + 1);
    if(k > n) {
        puts("0");
        return;
    }
    if(k == 0) {
        ll ans = 0;
        int cnt = 0;
        for(int i = 1; i <= n; ++i) {
            if(s[i] == '0')
                ++cnt;
            else {
                ans += 1ll * cnt * (cnt + 1) / 2;
                cnt = 0;
            }
        }
        ans += 1ll * cnt * (cnt + 1) / 2;
        printf("%lld\n", ans);
        return;
    }
    int cnt = 0;
    for(int i = 1; i <= n; ++i)
        cnt += (s[i] == '1');
    if(cnt < k) {
        puts("0");
        return;
    }
    ll ans = 0;
    int l = 1, r = 0;
    cnt = 0;
    while(1) {
        while(l <= n && s[l] == '0')
            ++l;
        if(l > n)
            break;
        //printf("[%d,%d] cnt=%d\n", l, r, cnt);
        if(r == 0) {
            r = l;
            cnt = 1;
        }
        //printf("[%d,%d] cnt=%d\n", l, r, cnt);
        while(r + 1 <= n && cnt + (s[r + 1] == '1') < k) {
            ++r;
            cnt += (s[r] == '1');
        }
        if(r + 1 <= n && s[r + 1] == '1' && cnt + 1 == k) {
            ++r;
            cnt += (s[r] == '1');
        }
        //printf("[%d,%d] cnt=%d\n", l, r, cnt);
        if(cnt == k) {
            int pre0 = 0, suf0 = 0;
            int pl = l - 1, pr = r + 1;
            while(pl >= 1 && s[pl] == '0') {
                ++pre0;
                --pl;
            }
            while(pr <= n && s[pr] == '0') {
                ++suf0;
                ++pr;
            }
            ans += 1ll * (pre0 + 1) * (suf0 + 1);
            //printf("l=%d r=%d\n", pre0, suf0);
        }
        cnt -= (s[l] == '1');
        ++l;
    }
    printf("%lld\n", ans);
}

23、dp

給定一組石頭,每個石頭有一個正數的重量。每一輪開始的時候,選擇兩個石頭一起碰撞,假定兩個石頭的重量為x,y,x<=y,碰撞結果為:

  1. 如果x==y,碰撞結果為兩個石頭消失
  2. 如果x!=y,碰撞結果兩個石頭消失,生成一個新的石頭,新石頭重量為y-x

最終最多剩下一個石頭為結束。求解最小的剩余石頭質量是多少。

數據范圍:所有石頭的重量總和不超過10000。

題解:並不是每次取出最大的兩個貪心,這樣會WA,正解是dp。注意到消除石頭的過程,非常像是給其中小的那個加負號,大的那個加正號,然后整體作為一個結果放回去。意思就是最終只需要給這些數字定正負,然后求這個值的絕對值的最小值。可以用個滾動數組記錄dp[i][j]為前i個石頭是否能組合出重量j,dp完成之后,得到一個全部石頭的重量sum,對於可以組合出的重量j(dp[n][j]為true),就嘗試用abs(j-(sum-j))更新答案。

24、暴力

在你面前有n個蓄水池,他們組成了樹形結構(由n-1條邊連接)。蓄水池節點編號從1開始到n。對每個蓄水池節點來說,他的兒子蓄水池節點都擺放在他的下面,並且和它用水管相連,根據重力,水會向下流動。現在我們要在蓄水池上做一些操作:

  1. 把節點v填滿水。然后v的所有兒子節點水也會被填滿。
  2. 清空節點v的水。然后v所有的父親節點水都會被清空。
  3. 詢問每個蓄水池節點是否有水。
    初始狀態時候,每個節點都是空的。
    現在我們會依次進行一系列操作,我們想提前知道每次操作后的結果,你能幫忙解決嗎?

數據范圍:n<=1000

題解:看樣例瞎猜樹的根是1,然后寫個dfs暴力更新。假如n變得更大,樹剖的 \(O(nlog^2n)\) 肯定是可行的一種方案,但是有沒有可能進行樹上差分呢?感覺是不行的。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM