後端開發 面試題整理

文章目錄

面向對象(C/C++)

引用和指針的區別

  • 都是地址的概念。
  • 指針指向一塊內存,它存儲的是所指內存的地址,而引用是某塊內存的別名。
  • 引用不可以爲空,但指針可以爲空。
  • 引用只能在定義時被初始化一次,之後不可變。
  • 指針需要解引用。

堆、棧

  • 棧:由操作系統自動分配釋放,存放函數的參數值、局部變量等。刪除與加入均在棧頂操作,是一種線性表。棧使用的是一級緩存,通常被調用時處於存儲空間中,調用完畢立即釋放,一種先進後出的數據結構。
  • 堆:堆是指程序運行時申請的動態內存,而不是在程序編譯時,分配方式類似於鏈表,棧只是指一種使用堆的方法。堆是放在二級緩存中,生命週期由虛擬機的垃圾回收算法決定,堆可以被看成是一棵樹。

STL_Map

底層是用紅黑樹實現的,查找的平均複雜度是log(n),map的key是有序的,因爲是用紅黑樹實現的,所以key是中序遍歷出來的。
hash_map是用hash實現的map,key是無序的,hash_map查詢是O(1)的。
hashtable就是手寫的hash表。

虛函數

用virtual關鍵字申明的函數叫做虛函數,是類的成員函數,虛函數是用於實現多態的機制,核心理念就是通過基類訪問派生類定義的函數,只能藉助於指針或者引用來達到多態的效果。
存在虛函數的類都有一個一維的虛函數表叫做虛表。當類中聲明虛函數時,編譯器會在類中生成一個虛函數表,其中存放着該類所有的虛函數對應的函數指針。

C++的構造函數可以是虛函數嗎?

構造函數不能爲虛函數,而析構函數常常是虛函數。

  1. 從存儲空間角度:
    虛函數對應一個虛表,vtable是存儲在對象的內存空間的。如果構造函數是虛的,就需要通過 vtable來調用,但是對象沒有實例化的時候就沒有內存空間,也就不存在vtable,所以構造函數不能是虛函數。
  2. 構造函數不允許是虛函數,因爲創建一個對象時需要明確對象的類型。但析構往往通過基類的指針來銷燬對象,如果析構函數不是虛函數,就不能正確識別對象類型從而不能正確調用析構函數。

解決哈希衝突的方法

  1. 開放定址法
    基本思想是:當關鍵字key的哈希地址p出現衝突時,以P爲基礎,產生另一個哈希地址P1,如果P1仍然衝突,再以P爲基礎,產生另一個哈希地址P2,直到找出一個不衝突的哈希地址Pi ,將相應元素存入其中。
  2. 鏈地址法
    將所有哈希地址爲 i 的元素構成一個同義詞鏈的單鏈表,並將單鏈表的頭指針存在哈希表的第i個單元中,因而查找、插入和刪除主要在同義詞鏈中進行。鏈地址法適用於經常進行插入和刪除的情況。
  3. 再哈希法
    當hash地址發生衝突時,再次計算hash值,直到不衝突爲止,這種方法不易產生聚集,但增加了計算時間。

C++的struct和類的區別

C++結構體的繼承默認是public,而c++類的繼承默認是private。
C++結構體內部成員變量及成員函數默認的訪問級別是public,而c++類的內部成員變量及成員函數的默認訪問級別是private。

面向對象的三大特性

  1. 封裝
    封裝就是隱藏對象的屬性和實現細節,僅對外公開接口,控制在程序中屬性的讀和修改的訪問級別。封裝的目的是增強安全性和簡化編程,使用者不必瞭解具體的實現細節,而只是要通過外部接口,以特定的訪問權限來使用類的成員。
  2. 繼承
    繼承就是子類繼承父類的特徵和行爲,使得子類對象具有父類的實例域和方法,或子類從父類繼承方法,使得子類具有父類相同的行爲。
  3. 多態
    同一個行爲具有多個不同表現形式或形態的能力,是指一個類實例的相同方法在不同情形有不同表現形式。

多態的實現

多態分爲靜態多態與動態多態。

  • 靜態多態就是重載,在編譯時就可以確定函數地址。
  • 動態多態就是通過繼承重寫基類的虛函數實現的多態,在運行時確定。運行時在虛函數表中尋找調用函數的地址。
  • 在基類的函數前加上virtual關鍵字,在派生類中重寫該函數,運行時將會根據對象的實際類型來調用相應的函數,如果對象類型是派生類,就調用派生類的函數,如果對象類型是基類,就調用基類的函數。
  • 存在虛函數的類都有一個一維的虛函數表叫做虛表,類的對象有一個指向虛表開始的虛指針。虛表是和類對應的,虛表指針是和對象對應的。

區分重載、重寫和隱藏。

  • 重載:同一類中,函數名相同,但參數列表不同。
  • 重寫:父子類中,函數名相同,參數列表相同,且有virtual修飾。
  • 隱藏:父子類中,函數名相同,參數列表相同,但沒有virtual修飾。

五大基本原則

  1. 單一職責原則:一個類只完成一個功能。
  2. 開放封閉原則:對象或實體應該對擴展開放,對修改封閉。
  3. 里氏替換原則:所有引用基類的地方必須能透明地使用其子類的對象。
  4. 依賴倒置原則:抽象不應該依賴於細節,細節應該依賴於抽象。
  5. 接口隔離原則:客戶端不應該依賴它不需要的接口,類間的依賴關係應該建立在最小的接口上

Struct 和 Union有下列區別:

  • 在存儲多個成員信息時,編譯器會自動給struct第個成員分配存儲空間,struct 可以存儲多個成員信息,而Union每個成員會用同一個存儲空間,只能存儲最後一個成員的信息。
  • 都是由多個不同的數據類型成員組成,但在任何同一時刻,Union只存放了一個被先選中的成員,而結構體的所有成員都存在。
  • 對於Union的不同成員賦值,將會對其他成員重寫,原來成員的值就不存在了,而對於struct 的不同成員賦值 是互不影響的。

define和const區別:

  • define是宏定義,程序在預處理階段將用define定義的內容進行了替換。因此程序運行時,常量表中並沒有用define定義的常量,系統不爲它分配內存,const定義的常量,在程序運行時在常量表中,系統爲它分配內存。
  • define定義的常量,預處理時只是直接進行了替換,所以編譯時不能進行數據類型檢驗。const定義的常量,在編譯時進行嚴格的類型檢驗,避免出錯。
  • define定義表達式時要注意“邊緣效應”,例如如下定義:#define N 2+3 我們預想的N值是5,我們 int a = N/2,我們預想的a的值是2,可實際上a的值是3。原因在於在預處理階段,編譯器將 a = N/2處理成了 a = 2+3/2

static

  1. 靜態變量是堆分配的,而動態變量是在棧上分配的。靜態變量只會初始化一次,在變量定義時就分定存儲單元並保持不變,直至整個程序結束
  2. 函數體內static變量的作用範圍爲該函數體,在類中的static成員屬於全局變量,靜態方法只能訪問類的static成員變量。

const

  1. const 常量:定義時就初始化,以後不能更改
  2. const修飾形參,表明它是一個輸入參數,在函數內部不能改變其值
  3. const修飾類成員函數:該函數對成員變量只能進行只讀操作

New/delete. Malloc/free.

  • new是先自動判斷大小再去申請內存,再調用構造函數,返回一個指向該對象的指針。失敗拋出異常。
  • delete是調用析構函數,然後釋放內存。
  • malloc申請你給的內存大小,返回這塊內存的指針(void*)。失敗返回NULL。
  • free是直接釋放內存和指針。
  • malloc有內存擴容,new沒有。

vector和list的區別

  • vector和數組類似,擁有一段連續的內存空間,但是vector是動態申請內存的,並且起始地址不變。能高效的進行隨機存取,時間複雜度爲o(1),但進行插入和刪除操作時,會造成內存塊的拷貝,複雜度爲o(n)。
  • list是由雙向鏈表實現的,因此內存空間是不連續的。只能通過指針訪問數據,所以list的隨機存取效率非常低,時間複雜度爲o(n),但由於鏈表的特點,能高效地進行插入和刪除。
  • 總之,如果需要高效的隨機存取,而不在乎插入和刪除的效率,使用vector;
    如果需要大量的插入和刪除,而不關心隨機存取,則應使用list。

常量指針和指針常量

int const* pconst int* p 是常量指針,也就是說這個指針指向的值不能變,但是這個指針的地址可以變。
int *const p 是指針常量,指針的地址不能變,但是指向的值可以變,所以要初始化。

內存泄漏

程序申請的內存空間,在使用完畢後未釋放,一直佔據內存單元。
解決方法:良好的代碼習慣,在涉及內存的程序段檢測內存泄漏。重載new和delete,主要思路是將分配的內存以鏈表的形式自行管理,使用完成之後從鏈表中刪除,程序結束時可檢查該鏈表,當中記錄了內存泄露的文件,所在文件的行數以及泄露的大小。

智能指針

作用是監控內存的使用以及釋放內存。首先指針只能是由兩個類構成,分爲引用計數類和指針類,其實智能指針的本質是類,但是看起來像指針。動態分配的資源,交給一個類對象去管理,當類對象聲明週期結束時,自動調用析構函數釋放資源,防止內存泄漏。

深拷貝和淺拷貝

  • 淺拷貝只是增加了一個指針指向已存在的內存地址,深拷貝是在計算機中開闢一塊新的內存地址用於存放複製的對象。
  • 使用深拷貝的情況下,釋放內存的時候不會因爲出現淺拷貝時釋放同一個內存的錯誤。

操作系統 -------------------------------------------------------------

進程和線程

  • 一個任務就是一個進程,比如打開一個瀏覽器就是啓動一個瀏覽器進程,有些進程不止同時做一件事,比如Word,它可以同時進行打字、拼寫檢查等。進程內的這些“子任務”就稱爲線程。
  • 一個進程擁有多個線程,操作系統調度的基本單位是線程
  • 線程 = 進程 – 共享資源
  • 進程雖然不是調度的基本單位,但也能被調度。

多線程的優點和缺點

優點

  1. 多線程技術使程序的響應速度更快 ,因爲用戶界面可以在進行其它工作的同時一直處於活動狀態
  2. 佔用大量處理時間的任務使用多線程可以提高CPU利用率,即佔用大量處理時間的任務可以定期將處理器時間讓給其它任務
  3. 多線程可以分別設置優先級以優化性能。

缺點

  1. 如果有大量的線程,會影響性能,因爲操作系統需要在它們之間切換.
  2. 更多的線程需要更多的內存空間
  3. 線程中止需要考慮對程序運行的影響.
  4. 通常塊模型數據是在多個線程間共享的,需要防止線程死鎖情況的發生

進程間通信的方式

管道、消息隊列、共享內存、套接字、信號量

  • 管道:通常指無名管道,是半雙工的通信(數據只能在一個方向上流動),具有固定的讀寫端。只能用於具有親緣關係的進程之間的通信(也是父子進程或者兄弟進程之間),可以看成是一種特殊的文件,並不屬於任何文件系統,只存在於內存中。
  • 消息隊列:消息隊列是消息的鏈表,進程A可以向隊列中寫數據,隊列中有數據了進程B就可以開始讀數據了。消息隊列克服了信號傳遞信息少、管道只能承載無格式字節流以及緩衝區大小受限等缺點。
  • 共享內存:共享內存就是一塊內存,在這塊內存上的數據可以共同修改和讀取,由於內存有隨機訪問的優勢,所以共享內存就成爲了進程間通信最快的方式。
  • 套接字:套接字是網絡編程的api,通過套接字可以不同的機器間的進程進行通信,常用於客戶端進程和服務器進程的通信。
  • 信號:操作系統通過信號來通知進程系統中發生了某種預先規定好的事件,它也是用戶進程之間通信和同步的一種原始機制。一個鍵盤中斷或者一個錯誤條件等都有可能產生一個信號。

線程間的同步方法

大體可分爲兩類:用戶模式和內核模式

  • 內核模式就是指利用系統內核對象的單一性來進行同步,使用時需要切換內核態與用戶態。內核模式下的方法有:互斥量、信號量、事件。
    • 互斥量: 能夠用於多個進程之間線程互斥問題,並且能完美的解決某進程意外終止所造成的“遺棄”問題
    • 信號量:它允許多個線程在同一時刻訪問同一資源,但是需要限制在同一時刻訪問此資源的最大線程數目
    • 事件:通過通知操作的方式來保持線程的同步,還可以方便實現對多個線程的優先級比較的操作
  • 用戶模式就是不需要切換到內核態,只在用戶態完成操作。用戶模式下的方法有:原子操作(例如一個單一的全局變量)、臨界區
    • 臨界區:在任意時刻只允許一個線程對共享資源進行訪問,如果有多個線程試圖訪問公共資源,那麼有一個線程進入後,其他試圖訪問公共資源的線程將被掛起,等到進入臨界區的線程離開,臨界區被釋放後,其他線程纔可以搶佔。

死鎖

  • 多個進程在運行過程中因爭奪資源而造成的一種僵局,當進程處於這種僵持狀態時,若無外力作用,它們都將無法再向前推進。
  • 產生的原因:競爭不可搶佔性資源、競爭可消耗資源、進程推進順序不當。
  • 必要條件:互斥條件、請求和保持條件、不可搶佔條件、循環等待條件。
    • 互斥條件:在一段時間內某資源僅爲一進程所佔用
    • 請求和保持條件:當進程因請求資源而阻塞時,對已獲得的資源保持不放
    • 不可搶佔條件:進程已獲得的資源在未使用完之前,不能剝奪,只能在使用完時由自己釋放
    • 循環等待條件:在發生死鎖時,必然存在一個進程-資源的環形鏈。
  • 處理的方法:預防死鎖、避免死鎖(銀行家算法)、檢測死鎖、解除死鎖。
    • 資源一次性分配:一次性分配所有資源,這樣就不會再有請求了(破壞請求條件)
    • 只要有一個資源得不到分配,也不給這個進程分配其他的資源:(破壞請保持條件)
    • 可剝奪資源:即當某進程獲得了部分資源,但得不到其它資源,則釋放已佔有的資源(破壞不可剝奪條件)
    • 資源有序分配法:系統給每類資源賦予一個編號,每一個進程按編號遞增的順序請求資源,釋放則相反(破壞循環等待條件)

內存中的堆和棧的區別

申請方式和回收方式不同

  • 棧上的空間是自動分配自動回收的,所以棧上的數據的生存週期只是在函數的運行過程中。
  • 堆上的數據只要程序員不釋放空間,就一直可以訪問到,缺點是忘記釋放會造成內存泄露。

申請後系統的響應

  • 只要棧的剩餘空間大於所申請空間,系統將爲程序提供內存,否則將報異常提示棧溢出。
  • 堆在申請的後,操作系統有一個記錄空閒內存地址的鏈表,當系統收到程序的申請時,會遍歷該鏈表,尋找第一個空間大於所申請空間的堆結點,然後將該結點從空閒結點鏈表中刪除,並將該結點的空間分配給程序。

申請效率的比較

  • 棧由系統自動分配,速度較快。但程序員是無法控制的。
  • 堆由new分配的內存,一般速度比較慢,而且容易產生內存碎片,不過用起來最方便。

申請大小的限制

  • 棧是向低地址擴展的數據結構,是一塊連續的內存的區域。在 Windows下,棧的大小是編譯時就確定的常數,如果申請的空間超過棧的剩餘空間時,將提示overflow。因此,能從棧獲得的空間較小。
  • 堆是向高地址擴展的數據結構,是不連續的內存區域。這是由於系統是用鏈表來存儲的空閒內存地址的,自然是不連續的,而鏈表的遍歷方向是由低地址向高地址。堆的大小受限於計算機系統中有效的虛擬內存。

堆和棧中的存儲內容

  • 在函數調用時,第一個進棧的是主函數中函數調用後的下一條指令(函數調用語句的下一條可執行語句)的地址,然後是函數的各個參數,參數是由右往左入棧的,然後是函數中的局部變量,靜態變量是不入棧的。
  • 當本次函數調用結束後,局部變量先出棧,然後是參數,最後棧頂指針指向最開始存的地址,也就是主函數中的下一條指令,程序由該點繼續運行。
  • 一般是在堆的頭部用一個字節存放堆的大小。堆中的具體內容有程序員安排。

存取效率的比較

  • 在運行時刻賦值的是放在棧中。
  • 在編譯時就確定的是放在堆中。

虛擬內存

  • 虛擬內存是計算機系統內存管理的一種技術。它使應用程序認爲它擁有連續的可用的內存,而實際上,它通常是被分隔成多個物理內存碎片,還有部分暫時存儲在外部磁盤存儲器上,在需要時進行數據交換。使用這種技術的系統使得大型程序的編寫變得更容易,對真正的物理內存的使用也更有效率。
  • 用戶程序開發方便、保護內核不受惡意或者無意的破壞、隔離各個用戶進程

進程調度算法

  • 先來先服務調度
  • 短作業優先調度
  • 優先級調度
  • 高響應比優先調度
  • 時間輪片調度
  • 多級反饋隊列調度

linux 系統中,一個被打開的文件可以被另一個進程刪除嗎?

可以。
Linux中每個文件都會有2個link計數器——i_count 和 i_nlink,刪除操作只是將 i_nlink 置爲 0 了,由於文件被進程引用的緣故,i_count 不爲 0,所以系統沒有真正刪除這個文件。i_nlink 是文件刪除的充分條件,而 i_count 纔是文件刪除的必要條件。

計算機網絡----------------------------------------------------------

TCP協議和UDP協議的區別是什麼

  • TCP協議是通過三次握手建立連接的,會話結束之後也要結束連接。而UDP是無連接的。
  • TCP協議保證數據按序發送,按序到達,提供超時重傳來保證可靠性。但是UDP不保證按序到達,甚至不保證到達,只是努力交付。
  • TCP協議所需資源多,TCP首部需要20個字節,UDP首部字段只需8個字節。
  • TCP有流量控制和擁塞控制。UDP沒有,網絡擁堵會影響發送端的發送速率
  • TCP是一對一的連接。而UDP則可以支持一對一,多對多,一對多的通信。
  • TCP面向的是字節流的服務。UDP面向的是報文的服務

TCP報文中的序號和確認號的作用?

TCP可靠傳輸的關鍵部分。

  • 序號是本報文段發送的數據組的第一個字節的序號。在TCP傳送的流中,每一個字節一個序號。例如一個報文段的序號爲300,此報文段數據部分共有100字節,則下一個報文段的序號爲400。所以序號確保了TCP傳輸的有序性。
  • 確認號,即ACK,指明下一個期待收到的字節序號,表明該序號之前的所有數據已經正確無誤的收到。確認號只有當ACK標誌爲1時纔有效。比如建立連接時,SYN報文的ACK標誌位爲0。

TCP三次握手

  • 客戶端向服務端發送連接請求,等待服務器確認
  • 服務器收到請求報文後,如果同意連接,則發出確認報文。
  • 客戶進程收到確認後,再次向服務器發送確認信息,確認鏈接,TCP連接建立。

爲什麼A還要發送一次確認呢?可以二次握手嗎?

防止已失效的連接請求報文段突然又傳送到了B,因而產生錯誤。

如A發出連接請求,但因連接請求報文丟失而未收到確認,於是A再重傳一次連接請求。後來收到了確認,建立了連接。數據傳輸完畢後,就釋放了連接。但是第一個丟失的報文段只是在某些網絡結點長時間滯留了,延誤到連接釋放後纔到達B,此時B誤認爲A又發出一次新的連接請求,於是就向A發出確認報文段,同意建立連接。不採用三次握手,只要B發出確認,就建立新的連接了,此時A不理睬B的確認且不發送數據,則B一致等待A發送數據,浪費資源。

四次揮手

  • 客戶端進程發出連接釋放報文,並且停止發送數據。
  • 服務器收到連接釋放報文,發出確認報文,服務端就進入了CLOSE-WAIT(關閉等待)狀態。客戶端收到服務器的確認請求後,客戶端就進入FIN-WAIT-2(終止等待2)狀態,等待服務器發送連接釋放報文
  • 服務器將最後的數據發送完畢後,就向客戶端發送連接釋放報文,由於在半關閉狀態,服務器很可能又發送了一些數據,此時,服務器就進入了LAST-ACK(最後確認)狀態,等待客戶端的確認。
  • 客戶端收到服務器的連接釋放報文後,必鬚髮出確認,此時,客戶端就進入了TIME-WAIT(時間等待)狀態。服務器收到了客戶端發出的確認,立即進入CLOSED狀態,結束這次的TCP連接。服務器結束TCP連接的時間要比客戶端早一些。

爲什麼連接的時候是三次握手,關閉的時候卻是四次握手

當服務器端收到客戶端的連接請求報文後,可以把SYN和ACK報文一起發送。其中ACK報文是用來應答的,SYN報文是用來同步的。但關閉連接時,當收到對方的FIN報文通知時,它僅僅表示對方沒有數據發送給你了,但服務器端未必所有的數據都全部發送給對方了,可能還需要發送一些數據給對方之後,再發送FIN報文給對方來表示現在可以關閉連接了。

TCP協議是靠什麼保證傳輸的可靠性的?

  • 校驗和:發送方在發送數據之前計算檢驗和,並進行校驗和的填充。接收方,收到數據後,對數據以同樣的方式進行計算,求出校驗和,與發送方的進行比對
  • 確認應答與序列號:TCP傳輸時將每個字節的數據都進行了編號,TCP傳輸的過程中,每次接收方收到數據後,都會對傳輸方進行確認應答,也就是發送ACK報文,報文中包含對應的確認序列號。
  • 超時重傳:發送方在發送完數據後等待一個時間,時間到達沒有接收到ACK報文,那麼對剛纔發送的數據進行重新發送
  • 連接管理:連接管理就是三次握手與四次揮手的過程
  • 流量控制:發送端的發送速度太快,導致接收端的結束緩衝區填充滿了,此時如果仍舊發送數據,那麼接下來發送的數據都會丟包。TCP就會根據接收端對數據的處理能力,決定發送端的發送速度,這個機制就是流量控制。
  • 擁塞控制:發送端在剛開始就發送大量的數據,那麼就可能造成網絡擁塞。所以TCP引入了慢啓動的機制,在開始發送數據時,先發送少量的數據探路。

TCP擁塞控制

  • 慢開始:在開始的時候發送的少,但是增長的速度是以指數的形式增長。
  • 擁塞避免:當擁塞窗口達到一個閾值時,窗口大小不再呈指數上升,而是以線性上升,避免增長過快導致網絡擁塞。
  • 快重傳:收到三個一樣的回覆報文的時候就重傳該文件

TCP流量控制

滑動窗口:窗口大小指的是無需等待確認應答而可以繼續發送數據的最大值,窗口越大, 則網絡的吞吐率就越高。
接收窗口只有在前面所有的段都確認的情況下才會移動左邊界,收到了一個返回確認的ACK之後窗口就往後移動繼續發送在窗口裏的數據。

五層協議的體系結構各層的主要功能

  • 應用層:應用層是體系結構中的最高層,應用層協議定義的是應用進程間通信和交互的規則,應用層的任務是通過應用進程間的交互來完成特定網絡應用,這裏的進程就是指正在運行的程序。
  • 運輸層:負責爲兩個主機中進程之間的通信。主要使用以下兩種協議:面向連接的傳輸控制協議TCP,和無連接的用戶數據報協議UDP。
  • 網絡層:網絡層爲分組交換網上不同主機提供通信服務。網絡層將運輸層產生的報文段或用戶數據報封裝成分組和包進行傳送。
  • 數據鏈路層:兩臺主機間的數據傳輸,是一段一段在數據鏈路上傳送的,需要專門的鏈路層協議,在兩個相鄰節點間的鏈路上傳送幀,每一幀包括數據和必要的控制信息(如差錯控制、流量控制)
    三個基本問題:封裝成幀,透明傳輸,差錯檢測
  • 物理層:透明地傳送比特流。在物理層上所傳數據的單位是比特。

一次完整的HTTP請求過程

  1. 域名解析:得到了域名對應的IP地址
  2. 瀏覽器發起HTTP請求
  3. 然後到傳輸層,選擇傳輸協議,TCP或者UDP,對HTTP請求進行封裝,加入了端口號等信息
  4. 然後到了網絡層,通過IP協議將IP地址封裝爲IP數據報,此時會用到ARP協議,主機發送信息時將包含目標IP地址的ARP請求廣播到網絡上的所有主機,並接收返回消息,以此確定目標的物理地址,找到目的MAC地址
  5. 接下來到了數據鏈路層,把網絡層交下來的IP數據報添加首部和尾部,封裝爲MAC幀,現在根據目的mac開始建立TCP連接,三次握手,接收端在收到物理層上交的比特流後,根據首尾的標記,識別幀的開始和結束,將中間的數據部分上交給網絡層,然後向上傳遞到應用層
  6. 服務器響應請求並請求客戶端要的資源,傳回給客戶端
  7. 斷開TCP連接,瀏覽器對頁面進行渲染呈現給客戶端。

數據結構--------------------------------------------------------------

跳錶

跳錶是redis的一個核心組件,也被廣泛地運用到了各種緩存地實現當中。它的主要優點就是可以跟紅黑樹、平衡樹一樣,做到比較穩定地插入、查詢與刪除。時間複雜度爲O(logN),代碼相對簡單。
img

紅黑樹

一種二叉查找樹,每個節點有標記顏色

  • 任意的左子樹和右子樹也分別爲二叉查找樹,若一節點的左子樹不爲空,那麼左子樹上所有節點的值均小於它的根節點的值,右子樹同理
  • 沒有鍵值相等的節點(鍵值爲節點編號和節點的值)
  • 根節點和爲空葉子結點都是黑的,如果一個節點是紅的,那麼它的子節點兩是黑的
  • 任意一結點到每個葉子結點的路徑都包含數量相同的黑結點

B樹、B-樹、B+樹和B*樹的區別

  • 二叉搜索樹:二叉樹,每個結點只存儲一個關鍵字,等於則命中,小於走左結點,大於走右結點。
  • B-樹:多路搜索樹,每個節點都會保存數據,關鍵字只會出現一次。有n棵子樹的非葉子結點中含有n-1個關鍵字。
  • B+樹:在B-樹基礎上,爲葉子結點增加鏈表指針,所有數據都保存在葉子結點中,非葉子結點作爲葉子結點的索引。有n棵子樹的非葉子結點中含有n個關鍵字,同一個關鍵字會在不同節點中重複出現。
  • B*樹:在B+樹基礎上,爲非葉子結點也增加指向兄弟的鏈表指針,將結點的最低利用率從1/2提高到2/3

B+樹的查詢優勢

  • B+樹的中間節點不保存數據,所以磁盤頁能容納更多節點元素,更“矮胖”
  • B+樹查詢必須查找到葉子節點,B樹只要匹配到即可不用管元素位置,因此B+樹查找更穩定,但是速度上並不慢
  • 對於範圍查找來說,B+樹只需遍歷葉子節點鏈表即可,B樹卻需要重複地中序遍歷

數據庫-----------------------------------------------------------------

數據庫索引的作用

數據庫索引是爲了增加查詢速度而對錶字段附加的一種標識。

  • 通過創建唯一性索引,可以保證數據庫表中每一行數據的唯一性。
  • 可以大大加快數據的檢索速度,加速表和表之間的連接,特別是在實現數據的參考完整性方面。
  • 在使用分組和排序子句進行數據檢索時,同樣可以顯著減少查詢中分組和排序的時間。
  • 通過使用索引,可以在查詢的過程中,使用優化隱藏器,提高系統的性能。

關於索引

應該建索引的字段:

  • 經常作爲查詢條件的字段
  • 外鍵
  • 經常需要排序的字段
  • 分組排序的字段

應該少建或者不建索引的字段:

  • 表記錄太少
  • 經常需要插入、刪除、修改的表
  • 表中數據重複且分佈平均的字段

mysql的主備模式

保持兩個數據庫的狀態自動同步。對任何一個數據庫的操作都自動應用到另外一個數據庫,始終保持兩個數據庫數據一致。

優點:

  1. 可以做災備,其中一個壞了可以切換到另一個。
  2. 可以做負載均衡,可以將請求分攤到其中任何一臺上,提高網站吞吐量。
  3. 讀寫分離,提供查詢業務

數據主從同步一致性解決方案

  • 同步複製:指主庫執行完一個事務,並且所有從庫都執行了該事務才返回給客戶端。因爲需要等待所有從庫執行完該事務才能返回,所以全同步複製的性能必然會收到嚴重的影響。
  • 異步複製:MySQL默認的複製是異步的,主庫在執行完客戶端提交的事務後會立即將結果返回給客戶端,並不關心從庫是否已經接收並處理
  • 半同步複製:介於異步複製和全同步複製之間,主庫在執行完客戶端提交的事務後不是立刻返回給客戶端,而是等待至少一個從庫接收到並寫到relay log中才返回給客戶端。提高了數據的安全性,同時也造成了延遲,這個延遲最少是一個TCP/IP往返的時間。所以,半同步複製最好在低延時的網絡中使用。

程序題

鏈表

單鏈表逆序

ListNode* reverseList(ListNode* head) {
	ListNode *cur = head;
    ListNode *tmp, *prev = NULL;
    while (cur) {
    	tmp = cur->next;
        cur->next = prev;
        prev = cur;
        cur = tmp;
    }
    return prev;
}

[LeetCode83] 有序鏈表去重

struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};

ListNode* deleteDuplicates(ListNode* head) {
	ListNode *left = head, *right;
	while(left) {
		right = left->next;
		if(right && left->val == right->val) {
			left->next = right->next;
			delete right;
		}
		else left = right;
	}
	return head;
}

二叉樹鏡像

遞歸實現:

void MirrorRecursively(TreeNode *pRoot)
{
    if(pRoot == NULL) return;
    TreeNode *pTemp = pRoot->left;
    pRoot->left = pRoot->right;
    pRoot->right = pTemp;
    if(pRoot->left) MirrorRecursively(pRoot->left);  
    if(pRoot->right) MirrorRecursively(pRoot->right); 
}

非遞歸實現:

void MirrorIteratively(TreeNode* pRoot)
{
	if(pRoot == NULL) return;
	stack<TreeNode*> stk;
	stk.push(pRoot);
	while(!stk.empty()) {
		TreeNode *pNode = stk.top(); stackTreeNode.pop();

		TreeNode *pTemp = pNode->left;
		pNode->left = pNode->right;
		pNode->right = pTemp;

		if(pNode->left) stackTreeNode.push(pNode->left);
		if(pNode->right) stackTreeNode.push(pNode->right);
	}
}

調整一棵二叉樹,要求所有節點的右子樹的最大值大於左子樹的最大值

int maxV(BTNode *root){
	if(root == null) return INF;
    if(maxV(root.left) > maxV(root.right)) swap(root.left, root.right);
    return max(root.v, maxV(root.left), maxV(root.left));
}

二叉樹的三序遍歷

  • 先序
void preOrder2(BinTree *root) {  // 非遞歸實現前序遍歷 
    stack<BinTree*> s;
    BinTree *p = root;
    while(p != NULL || !s.empty()) {
        while(p != NULL) {
            cout << p->data << " ";
            s.push(p);
            p = p->lchild;
        }
        if(!s.empty()) {
            p = s.top(); s.pop();
            p = p->rchild;
        }
    }
}
  • 中序
void inOrder2(BinTree *root) {   // 非遞歸中序遍歷
    stack<BinTree*> s;
    BinTree *p = root;
    while(p != NULL || !s.empty()) {
        while(p != NULL) {
            s.push(p);
            p = p->lchild;
        }
        if(!s.empty()) {
            p = s.top(); s.pop();
            cout << p->data <<" ";
            p=p->rchild;
        }
    }    
}
  • 後序
vector<int> postOrder(TreeNode *root) {
    vector<int> res;
    if(root == NULL) return res;
    TreeNode *p = root;
    stack<TreeNode *> sta;
    TreeNode *last = root;
    sta.push(p);
    while (!sta.empty()) {
        p = sta.top();
        if((p->left == NULL && p->right == NULL) 
        		|| (p->right == NULL && last == p->left) 
        		|| (last == p->right)) {
            res.push_back(p->val);
            last = p;
            sta.pop();
        }
        else {
            if(p->right) sta.push(p->right);
            if(p->left) sta.push(p->left);
        }
    }
    return res;
}

算法題

  1. 隨機打亂數組,讓概率儘可能相同
  2. 代碼題,給一個鏈表,不知道長度,想要隨機取一個node,使得每個node被取的概率都是 1/len
  3. 實際工程問題,給一些員工,一些project,一些task,每一個task屬於一個project,每個員工都可以去任意project裏面取任意取任意個task來做,平均每個task需要工作15s, 每一個task的領取時間和提交時間有,現在想詢問一個員工一天工作了多長時間。
  4. 一個無序數組找其子序列構成的和最大,要求子序列中的元素在原數組中兩兩都不相鄰
  5. 現有一個隨機數生成器可以生成0到4的數,現在要讓你用這個隨機數生成器生成0到6的隨機數,要保證生成的數概率均勻。
  6. 有 N 枚棋子,每個人一次可以拿1到 M 個,誰拿完後棋子的數量爲0誰就獲勝。現在有1000顆棋子,每次最多拿8個,A 先拿,那麼 A 有必勝的拿法嗎?第一個人拿完後剩餘棋子的數量是8的倍數就必勝,否則就必輸。
  7. 給出一棵二叉樹的根節點,現在有這個二叉樹的部分節點,要求這些節點最近的公共祖先。
  8. 缺失的第一個正數(leetcode第41題)
  9. 求最長上升子序列的個數
  10. 非遞歸遍歷二叉樹
  11. 定一個二叉樹,原地將它展開爲鏈表
  12. 給定一棵二叉樹,想象自己站在它的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值
  13. 合併兩個有序鏈表。遞歸和非遞歸的實現
    將雙向鏈表按奇偶結點分開,形成兩個鏈表並返回
    如何計算一個包含重複元素的數組中不同元素的個數
    如何利用快排對一個單鏈表進行排序
    給一個出棧序列長度爲n,有多少種入棧的可能
    兩個棧模擬隊列
    一個二叉樹,每個節點除了有左右子節點外,還有指向父節點的引用。給出一個節點,返回它在二叉樹中中序遍歷的下一個節點。

用rand(5)等概率地生成rand(7)

參考博客:https://blog.csdn.net/u010025211/article/details/49668017

int Rand7(){
    while(x > 21) // x > 7 會導致[8, 25]的數都被浪費掉
        x = 5 * (rand(5) - 1) + rand(5) // x = rand(25)
    return x%7 + 1;
}

用 rand(a) 和 rand(b) 生成 rand(a * b):

rand(a*b) = a * (rand(b) - 1) + rand(a)

d層樓,e個雞蛋,最少嘗試次數

有一棟樓共100層,一個雞蛋從第N層及以上的樓層落下來會摔破, 在第N層以下的樓層落下不會摔破。給你2個雞蛋,設計方案找出N,並且保證在最壞情況下, 最小化雞蛋下落的次數。(假設每次摔落時,如果沒有摔碎,則不會給雞蛋帶來損耗)

	for(int i = 1; i <= d; i++) dp[1][i] = i;
	for(int i = 1; i <= e; i++) dp[i][1] = 1;
	for(int i = 2; i <= e; i++)
	{
		for(int j = 2; j <= d; j++)
		{
			int MIN = 1 + max(dp[i - 1][0], dp[i][j - 1] + 1);
			for(int k = 1; k <= j; k++)
			{
				MIN = min(MIN, 1 + max(dp[i - 1][k - 1], dp[i][j - k]));
			}
			dp[i][j] = MIN;
		}
	}
	printf("%d\n", dp[e][d]);

用2x1型和1x1型兩種積木,擺滿n行m列,有多少種擺法

提示:先考慮2行m列有多少種擺法,再算n行m列

對於每列來說情況相同,對於搭一列的問題,就是走樓梯問題,一次可以走一步或者兩步,總共有多少種走法。

dp[i][0] += dp[i-1][0..3]
dp[i][1] += dp[i-1][0, 2]
dp[i][2] += dp[i-1][0, 1]
dp[i][3] += dp[i-1][0]

兩個升序數組,找出第k小的數字

int get_kth_smallest(vector<int>& nums1, int st1, const int& sz1, vector<int>& nums2, int st2, const int& sz2, int k) {
        // 保證nums1.size() <= nums2.size(),方便分析
        if (sz1-st1 > sz2-st2) return get_kth_smallest(nums2, st2, sz2, nums1, st1, sz1, k);
        if (sz1 - st1 == 0) return nums2[k-1 + st2];
        if (1 == k) return nums1[st1] < nums2[st2]? nums1[st1]:nums2[st2];
        
        // 在nums1和nums2中分別取第k/2個數字。如果超出邊界,取最後那個數字
        int k1 = min(sz1-st1, k/2);
        int k2 = min(sz2-st2, k/2);
        // 如果nums1[k1-1] < nums2[k2-1],說明nums1[k1-1]小於要找的第k小
        if (nums1[st1+k1-1] < nums2[st2+k2-1]) return get_kth_smallest(nums1, st1+k1, sz1, nums2, st2, sz2, k-k1);
        // 如果nums1[k1-1] > nums2[k2-1],說明nums2[k2-1]小於要找的第k小
        // 如果nums1[k1-1] == nums2[k2-1],說明nums2[k2-1]小於等於要找的第k小。但是依然可以刪掉它,因爲還有nums1[k1-1]和它相等
        else return get_kth_smallest(nums1, st1, sz1, nums2, st2 + k2, sz2, k-k2);
        }
    }

將數組順時針旋轉90°

// 先按照主對角線反轉, 再按照中垂線反轉
void rotate(vector<vector<int>>& matrix) {
        int n = matrix.size();
        for (int i = 0; i < n; ++i) {
            for (int j = i + 1; j < n; ++j) {
                swap(matrix[i][j], matrix[j][i]);
            }
            reverse(matrix[i].begin(), matrix[i].end());
        }
    }

二叉樹上找LCA

  • 有father版:直接求深度, 然後深度大的先跳, 再一起跳。
  • 無father版:遞歸回溯時找到節點就返回給父親節點,當父親節點get到了兩個點就是答案
  • 二叉排序樹版, 從樹的根結點出發遍歷樹,如果當前結點都大於輸入的兩個結點,則下一步遍歷當前結點的左子樹;如果當前結點小於輸入的兩個結點,則下一步遍歷當前結點的右子樹。一直遍歷到當前結點比一個輸入結點大而比另一個小的時候,此時當前結點就是符合要求的最低公共祖先。

給定一個數組,將所有0元素移動到它的末端,同時保持非零元素的相對順序

雙指針, 從前往後掃即可。

判斷一棵二叉樹是否是平衡二叉樹

滿足以下兩點的就是平衡二叉樹:
1.左右子樹的高度差不能超過1
2.左右子樹也是平衡二叉樹

int IsBalance(BNode *root,int *pHeight) {
	if(root == NULL) { *pHeight = 0; return 1; }
	int leftHeight, rightHeight;
	int leftBalance = IsBalance(root->left, &leftHeight);
	int rightBalance = IsBalance(root->right, &leftHeight);
	*pHeight = max(leftHeight, rightHeight) + 1;
	if(leftBalance == 0 || rightBalance == 0) return 0;
	if(abs(leftHeight - rightHeight) > 1) return 0;
	return 1;
}

一個數組,每個位置的值對應下標。重新排列後要求對應位置上的值不能和下標相同,計算方案數

錯排公式:D[n]=(n1)(D[n1]+D[n2])D[n] = (n - 1) * (D[n - 1] + D[n - 2]),其中D[1]=0,D[2]=1D[1] = 0, D[2] = 1D[n]D[n] 表示 n個元素全部錯排的方法數。

推導過程:

  • 如果現在n - 1個新郎全錯排,現在添加一對夫婦,新郎的位置可以任意與n - 1個新郎換都可以達到全錯排,有(n1)D(n1)(n - 1) * D(n - 1)種情況。
  • 如果n - 1個新郎不是全錯排,要再添加一對夫婦後實現全錯排需要滿足:n - 1中只有一個人找到了他的新娘(即n - 2個人實現了全錯排),這時只要這個沒有錯排的人和第n個交換,就會實現全錯排。由於找到新娘的這個人可以是n-1其中任意一位,所以一共有(n - 1) * D(n - 2)種情況
  • 綜上情況相加:故D[n] = (n - 1) * (D[n - 1] + D[n - 2])

輸出k對括號的全部正確匹配方案

  • k = 1時,一個括號只有一種可能,就是()
  • k = 2時,可以在1的基礎上在其左邊加一對括號、在右邊加一對括號或者加的括號包住1,即()()、()()、(()),去掉重複就剩下兩種
set<string> solve(int n) {
	set<string> s; // 用set去重
	if(n == 1) {
		s.add("()");
		return s;
	}
	else {
		set<string> s2 = solve(n - 1);
		for(string now : set2) {
			s.add("()" + now);
			s.add(now + "()");
			s.add("(" + now + ")");
		}
		return s;
	}
}

將一些柱子整齊的立在一行,高度存在數組height[]中,然後往凹下去的地方倒水,問一共能蓄多少單位水

比如[5,1,3,4,5,1,3],答案是7 + 2 = 9
維護每一個柱子左右的最高柱子, 當前柱子的存水量就是 min(左最大值, 右最大值)。

[POJ 2796] 求一個區間,使得區間和乘以區間最小值最大

單調棧 掃兩遍

設計一個類,只能生成該類的一個實例

public class Singleton{
	private static Singleton instance = null;
	private(){
	}
	public static Singleton getInstance(){
		if(instance = null) {
			instance = new Singleton();
		}
		return instance;
	}
}

求數組中逆序對

歸併排序

//參考紫書算法競賽入門經典 
void merge_sort(int *A, int l, int r, int *T){ //[l, r) 排序. 外部調用區間爲[0, n)
	if(r - l > 1){
		int mid = l+(r-l)/2;
		int p = l, q = mid, now = l;
		// 對左右兩部分區間分別歸併排序
		merge_sort(A, l, mid, T); 
		merge_sort(A, mid, r, T);
		// 合併左右兩部分
		while(p < mid || q < r){
			if(q >= r ||  (p < mid && A[p] <= A[q])){
				T[now++] = A[p++];
			}
			else{
				T[now++] = A[q++];
				cnt += mid - p; // cnt記錄的是逆序對個數
			}
		}
		for(int i = l; i < r; i++) A[i] = T[i];
	}
}

快速排序的穩定化算法

  • 普通快速排序
#include <iostream>
 
using namespace std;
 
void Qsort(int a[], int low, int high)
{
    if(low >= high) return;
    int first = low;
    int last = high;
    int key = a[first];/*用字表的第一個記錄作爲樞軸*/
 
    while(first < last)
    {
        while(first < last && a[last] >= key) last--;
        a[first] = a[last];/*將比第一個小的移到低端*/
        while(first < last && a[first] <= key) first++;
        a[last] = a[first];    
/*將比第一個大的移到高端*/
    }
    a[first] = key;/*樞軸記錄到位*/
    Qsort(a, low, first-1);
    Qsort(a, first+1, high);
}
int main()
{
    int a[] = {57, 68, 59, 52, 72, 28, 96, 33, 24};
 
    Qsort(a, 0, sizeof(a) / sizeof(a[0]) - 1);/*這裏原文第三個參數要減1否則內存越界*/
 
    for(int i = 0; i < sizeof(a) / sizeof(a[0]); i++)
    {
        cout << a[i] << "";
    }
     
    return 0;
}/*參考數據結構p274(清華大學出版社,嚴蔚敏)*/
  • 穩定方法

100w個數 找最大top100

用小根堆維護, 當前值大於隊內最小值就替換。

堆排序

  • 堆排序是利用堆這種數據結構而設計的一種排序算法,堆排序是一種選擇排序,它的最壞,最好,平均時間複雜度均爲O(nlogn),它也是不穩定排序。首先簡單瞭解下堆結構。
include <iostream>
#include <algorithm>
using namespace std;
 
void max_heapify(int arr[], int start, int end) {
    //建立父節點指標和子節點指標
    int dad = start;
    int son = dad * 2 + 1;
    while (son <= end) { //若子節點指標在範圍內才做比較
        if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點大小,選擇最大的
            son++;
        if (arr[dad] > arr[son]) //如果父節點大於子節點代表調整完畢,直接跳出函數
            return;
        else { //否則交換父子內容再繼續子節點和孫節點比較
            swap(arr[dad], arr[son]);
            dad = son;
            son = dad * 2 + 1;
        }
    }
}
 
void heap_sort(int arr[], int len) {
    //初始化,i從最後一個父節點開始調整
    for (int i = len / 2 - 1; i >= 0; i--)
        max_heapify(arr, i, len - 1);
    //先將第一個元素和已經排好的元素前一位做交換,再從新調整(剛調整的元素之前的元素),直到排序完畢
    for (int i = len - 1; i > 0; i--) {
        swap(arr[0], arr[i]);
        max_heapify(arr, 0, i - 1);
    }
}
 
int main() {
    int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
    int len = (int) sizeof(arr) / sizeof(*arr);
    heap_sort(arr, len);
    for (int i = 0; i < len; i++)
        cout << arr[i] << ' ';
    cout << endl;
    return 0;
}

Partition方法求數組第k大的數

#include <iostream>
 
using namespace std;
 
int Partition(int* A,int left,int right){
    int key=A[left];
    while(left<right){
        while(left<right && A[right]>=key)
            right--;
        if(left<right)  A[left]=A[right];
 
        while(left<right && A[left]<=key)
            left++;
        if(left<right)  A[right]=A[left];
    }
    A[left]=key;
    return left;
}
 
int findKthNum(int* A,int left,int right,int k){
    int index=Partition(A,left,right);
    if(index+1==k)
        return A[index];
    else if(index+1<k)
        findKthNum(A,index+1,right,k);
    else
        findKthNum(A,left,index-1,k);
}
 
int main()
{
    int A[]={2,3,5,1,6,7,4};
    int len=sizeof(A)/sizeof(A[0]);
 
    cout << findKthNum(A,0,len-1,7) << endl;
    return 0;
}

約瑟夫環

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-MHAKF7vd-1585387853397)(/Users/dreamstart/Downloads/media/15516222282812.jpg)]

從1到n整數中1出現的次數

class Solution {
public:
    int countDigitOne(int n) {
        long long base = 1;
        int count = 0;
        while(base <= n){
            int round = n / (base * 10);
            int now = n / base % 10;
            int after = n % base;
            count += round * base;
            if(now == 1) count += after + 1;
            else if(now >= 2) count += base;
            base *= 10;
        }
        return count;
    }
};

le(left<right){
while(left<right && A[right]>=key)
right–;
if(left<right) A[left]=A[right];

    while(left<right && A[left]<=key)
        left++;
    if(left<right)  A[right]=A[left];
}
A[left]=key;
return left;

}

int findKthNum(int* A,int left,int right,int k){
int index=Partition(A,left,right);
if(index+1==k)
return A[index];
else if(index+1<k)
findKthNum(A,index+1,right,k);
else
findKthNum(A,left,index-1,k);
}

int main()
{
int A[]={2,3,5,1,6,7,4};
int len=sizeof(A)/sizeof(A[0]);

cout << findKthNum(A,0,len-1,7) << endl;
return 0;

}


#### 約瑟夫環

[外鏈圖片轉存中...(img-MHAKF7vd-1585387853397)]

#### 從1到n整數中1出現的次數

```c++
class Solution {
public:
    int countDigitOne(int n) {
        long long base = 1;
        int count = 0;
        while(base <= n){
            int round = n / (base * 10);
            int now = n / base % 10;
            int after = n % base;
            count += round * base;
            if(now == 1) count += after + 1;
            else if(now >= 2) count += base;
            base *= 10;
        }
        return count;
    }
};

LeetCode

[LeetCode76] 最小覆蓋子串:滑動窗口

在字符串 S 裏面找出,包含 T 所有字母的最小子串。

string minWindow(string s, string t) {
	int S = s.size() - 1, T = t.size();
	for(int i = 0; i < T; i++) vis[t[i]]++;
	int l = 0, r = -1, cnt = 0, ans = S + 1, ansl = 0, ansr = -1;
	while(l <= S) {
		while(r < S && cnt < T) {
			r++;
			if(tmp[s[r]] < vis[s[r]]) cnt++;
			tmp[s[r]]++;
		}
		if(cnt == T && r - l + 1 <= ans) ans = r - l + 1, ansl = l, ansr = r;
		if(tmp[s[l]] <= vis[s[l]]) cnt--;
		tmp[s[l]]--;
		l++;
	}
	string str = "";
	for(int i = ansl; i <= ansr; i++) str += s[i];
	return str;
}

[LeetCode] Longest Increasing Path in a Matrix

[LeetCode面試題32I II III] 從上到下打印二叉樹

  • 同一層的節點按照從左到右的順序打印。
// 按層 BFS即可
class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        vector<int> ans;
        if(root == NULL) return ans;
        queue<TreeNode*> que;
        TreeNode *cur = root;
        que.push(cur);
        while(!que.empty()) {
            cur = que.front(); que.pop();
            ans.push_back(cur->val);
            if(cur->left) que.push(cur->left);
            if(cur->right) que.push(cur->right);
        }
        return ans;
    }
};
  • 同一層的節點按從左到右的順序打印,每一層打印到一行
// BFS 過程中特殊處理每一行的節點
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int> > ans;
        if(root == NULL) return ans;
        TreeNode *cur = root;
        queue<TreeNode*> que;
        que.push(cur);
        while(!que.empty()) {
            vector<int> now;
            int S = que.size(); // que.size() 就是當前深度的節點數
            for(int i = 0; i < S; i++) {
                cur = que.front(); que.pop();
                now.push_back(cur->val);
                if(cur->left) que.push(cur->left);
                if(cur->right) que.push(cur->right);
            }
            ans.push_back(now);
        }
        return ans;
    }
};
  • 按照之字形順序打印二叉樹:reverse一下即可
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章