開心一刻
一天,朋友胃疼的難受,陪他去醫院
醫生:這些天你都吃了什麼?
朋友:媳婦剩的飯我吃,孩子剩的飯我也吃
醫生:你家不養狗的嗎?
朋友:難道狗剩下的我也要吃?
我當場就笑岔氣了
資料結構
關於什麼是連結串列,本文不做過多介紹,不瞭解的小夥伴自行去充能
稍微帶大家回顧下連結串列的分類,不做過多介紹,直接看圖
單連結串列
雙向連結串列
迴圈連結串列
單向迴圈連結串列
雙向迴圈連結串列
環形連結串列
由單連結串列 + 單向迴圈連結串列組成
花式玩法
後續的場景都會基於某些特定型別的連結串列,大家不要太放飛自我
我也會在各個場景中明確指明基於那個型別,大家不要看偏了
單向節點 OneWayNode
雖然程式碼用 java 實現,但涉及到的演算法實現是通用的,希望大家不要被開發語言所禁錮
連結串列反轉
基本上指的是單連結串列的反轉,大家就認為是單連結串列的反轉
樓主在以往的面試過程中就遇到過這個問題,而且不止一次被面到
如果大家連這個都不會,趕緊偷摸 code 起來
遞迴實現,實現簡單,也好理解
有遞迴,往往有其相愛相殺的迭代
不管是遞迴還是迭代,變數賦值的順序不是隨便的,不信你們改變下順序試試
如果沒有任何限制,反轉實現方式非常多;但面試時,往往會對時間複雜度或空間複雜度做一個極致的考量
這道題如果出現在面試中,那麼考核點就是:時間複雜度 O(N) ,額外空間複雜度 O(1) ,那麼你們覺得遞迴的實現會讓面試官滿意嗎?
實際開發工程中,反轉往往不需要大家手動去實現,高階程式語言基本都有已經實現好的工具方法,大家直接用就好
例如 java 中有工具方法: Collections.reverse ,有興趣的可以去跟下自己所用語言的實現,你會有驚喜,會發現有意思的新大陸
迴文判斷
何謂迴文,就是反轉之後與之前本身一樣,例如 a,b,b,a 、 1,2,3,2,1
針對該問題,大家第一時間想到的肯定是二分法,但二分法是基於陣列的,它不適用於單連結串列
那麼如何判斷單連結串列的迴文了
那就按回文的描述那樣,對原連結串列進行拷貝,然後反轉拷貝的連結串列,再將原連結串列與新連結串列的值逐一對應比較,過程類似如下
程式碼如下
有個資料結構,先進後出,也適用於這個場景,這個資料結構就是:棧;直接上程式碼
利用棧的方式,可以優化,其實只需要入棧連結串列右半側的資料即可
如何控制只入棧連結串列右半側的資料了,需要用到快慢指標
快慢指標的起點都是頭結點,慢指標每次移動一個,快指標一次移動兩個,當快指標走完的時候,慢指標來到中間位置
將慢指標所在的連結串列元素以及慢指標之後的連結串列元素入棧
上述的三種方式,不管是哪一種,額外空間複雜度都是 O(N) ,那有沒有額外空間複雜度為 O(1) 的方式了
有,同樣用快慢指標,只是快指標走完後,慢指標以及它之後的連結串列元素不是入棧,而是反轉,將反轉後的連結串列與原連結串列逐一對應比較,如下圖所示
程式碼實現
除了有限幾個變數,沒有使用額外的空間,那麼額外空間複雜度就是 O(1)
入環節點
給定一個單向連結串列(單連結串列或環形連結串列中的某一種),判斷它是否成環,不成環返回 null ,成環則返回入環的第一個節點
單連結串列返回 null ,環形連結串列則返回入環的第一個節點
對於題意,相信大家已經理解,那麼如何用程式碼實現了?
藉助 雜湊表 ;遍歷連結串列,將節點逐個放入 雜湊表 ,放入的時候判斷節點是否已存在
若存在,那麼該節點就是入環的第一個節點,若不存在,則將節點放入 雜湊表
如若連結串列能遍歷完,則說明沒有成環,直接返回 null
我們來看程式碼
就結果而言是對的,但卻用了 O(N) 的額外空間複雜度,這往往不是面試官想要的,他想要的往往是 O(1) 的額外空間複雜度
有沒有什麼辦法可以做到了,肯定是有的: Floyd判圈演算法
關於 Floyd判圈演算法 ,大家自行去百度,它有一個結論:快慢指標第一次在環中相遇時,其中一個指標回到起點,然後兩個指標同時一次走一步向後移動,當它們再次相遇時,一定是在第一個入環節點
我們來簡單證明一下這個結論,如下圖
第一次相遇之前,慢指標一次走一步,快指標一次走兩步,那麼第一次相遇時,快指標走的距離 FD = p + f * c + m,慢指標所走的距離 SD = p + s * c + m
其中 f 表示快指標在環中走的完整圈數,s 表示慢指標在環中走的完整圈數
所以 FD = 2 * SD,則有 p + f * c + m = 2 * (p + s * c + m),得到 p + m = (f - 2s) * c
f - 2s 肯定是一個 >= 0 的整數,所以 p + m 是環形周長的倍數
快慢指標第一次相遇後,快指標回到起點,慢指標停在相遇點(M),然後快慢指標都每次走一步向後移動
當快指標來到 P ,快指標走過的距離 FD = p,慢指標走過的距離 SD = p
因為慢指標是從 M 開始移動的,而 P 離 M 的距離為 m,所以相當於慢指標從 P 開始移動了 p + m 距離
而前面得出 p + m 就是環形周長的倍數,所以可以理解成慢指標從 P 開始,移動了環形周長倍數的距離,最終還是來到 P
所以快慢指標相遇於 P,也就是第一個入環節點
當理解了 Floyd判圈演算法 後,程式碼實現就很簡單了
僅僅用了快慢指標兩個額外變數,額外空間複雜度 O(1)
這個題其實還有一個變種:如果成環,請返回環的大小(環中有多少個節點)
求環的大小比找入環的第一個節點要更好理解一點,當快慢指標在環中第一次相遇時,計時器初始成 0,一個指標不動,另一個指標逐步向後移動
每移動一步計數器就加 1,當快慢指標再次相遇時,計數器的值就是環的大小;程式碼就不演示了,大家自行去 code 、 code 、 code !
相交節點
給定兩個單向連結串列(單連結串列或環形連結串列),不相交則返回 null ,相交則返回相交的第一個節點
藉助雜湊表,實現比較簡單,也容易理解,直接看程式碼
額外空間複雜度 O(N) ,這往往不是面試官想要的結果,那有沒有什麼方式,其額外空間複雜度是 O(1) 了,我們往下看
我們先來捋一下兩個單向連結串列的關係有哪些
兩個單連結串列
如果兩個都是單連結串列,那麼他們之間的關係就只有如下兩種
如果兩個單連結串列相交,那麼從第一個相交節點開始,後面的節點都是共用的
所以我們可以如下處理,先找到兩個連結串列的尾節點,如果這兩個尾節點不是同一個節點,那麼肯定不相交,直接返回 null
找尾節點的過程中,記錄下兩個連結串列各自的長度:L1、L2,長的連結串列先移動 | L1-L2 |,然後兩個連結串列同時移動,一次移動一步
移動的過程中,一旦節點是同一個節點,那麼該節點就是相交的第一個節點,直接返回該節點
結合程式碼,更好理解
只用到了有限的幾個變數,那麼額外空間複雜度 O(1)
一個單連結串列,一個環形連結串列
因為連結串列節點是單向的,所以這兩個連結串列不可能相交
大家不要無限放大你們豐富的想象力,各種意淫這兩個連結串列的相交情況,結合實際情況去手動畫你們腦海中想象出來的相交情況
連結串列節點就一個 next 、一個 next 、一個 next !
兩個環形連結串列
此時兩個連結串列的關係,無非就下面三種
兩個環形連結串列的三種情況其實都和入環節點有關係,假設 h1 的入環節點是 loop1,h2 的入環節點是 loop2
如果 loop1 == loop2,對應情況 2,此時相交的第一個節點肯定在 h1 ~ loop1 或 h2 ~ loop2 之間
我們可以把 h1 ~ loop1 看成一個單連結串列,h2 ~ loop2 看成第二個單連結串列
此時大家是不是想起了什麼,往上滑一滑,去看看 兩個單連結串列 的相交
如果 loop1 != loop2,對應情況 1、3
從 loop1 的下一個節點開始,一次走一步,如果回到了 loop1 還未遇到 loop2,那麼兩個連結串列不相交
如果回到 loop1 之前遇到了 loop2,那麼說明兩個連結串列相交,第一個相交的節點就是 loop1 或 loop2
我們來看看程式碼
把 兩個單向連結串列 的三種關係串起來
額外空間複雜度 O(1)
有沒有覺得很好玩 ?
總結
1、一個題的實現方式往往有多種,但面試中往往考核的是時間複雜度或空間複雜度的極致利用
2、快慢指標在連結串列中很重要,希望大家能夠建立起快慢指標的概念
3、連結串列的花式玩法非常多,有興趣的可以去力扣上刷一刷:連結串列