數(shù)據(jù)結(jié)構(gòu)基礎(chǔ)--鏈表

目錄

  • 基本性質(zhì)
  • 鏈表的分類
    • 按連接方向分類
    • 按照有無循環(huán)分類
  • 鏈表問題代碼實現(xiàn)的關(guān)鍵點
  • 鏈表插入和刪除的注意事項
  • 鏈表翻轉(zhuǎn)
  • 向一個有序的環(huán)境鏈表中插入一個節(jié)點氓侧,并保持依舊有序
  • 對于一個單鏈表,在不給定head的情況下刪除指定node导狡。要求時間復(fù)雜度O(1)
  • 給定一個鏈表约巷,與一個數(shù)組num。要求實現(xiàn)荷蘭國旗
  • 給定兩個有序鏈表的head旱捧,打印共同部分
  • 給定一個單鏈表的head独郎,實現(xiàn)一個調(diào)整鏈表的函數(shù),使得每K個節(jié)點之間逆序枚赡,如果最后不足K個氓癌,則不調(diào)整
  • 判斷一個鏈表是否為回文結(jié)構(gòu)
  • 判斷一個單鏈表是否有環(huán),如有則返回入環(huán)節(jié)點贫橙。時間復(fù)雜度O(N)贪婉,額外空間復(fù)雜度O(1)
  • 兩個無環(huán)單鏈表是否相交,時間復(fù)雜度O(N+M)料皇,額外空間復(fù)雜度O(1)
  • 判斷兩個有環(huán)單鏈表是否相交谓松,時間復(fù)雜度O(N+M)星压,額外空間復(fù)雜度O(1)
  • 判斷兩個鏈表是否相交践剂,并返回第一個相交的節(jié)點

基本性質(zhì)

  • 鏈表問題算法難度不高,但考察代碼的實現(xiàn)能力
  • 鏈表和數(shù)組一樣娜膘,都是一種線性結(jié)構(gòu)
  1. 數(shù)組是物理地址上一段連續(xù)的存儲空間逊脯。
    可以通過下標(biāo)直接獲取元素
    當(dāng)內(nèi)容超出容量時需要重新定義數(shù)組。
  2. 鏈表空間不一定保持聯(lián)系竣贪,為臨時分配的军洼。
    只能從鏈表的頭部開始一個一個查找
    增刪的效率高于數(shù)組,因為不需要更改內(nèi)存結(jié)構(gòu)

鏈表的分類

  • 按連接方向分類
  1. 單鏈表
    每個節(jié)點只能通過next指針演怎,指向下一個節(jié)點匕争。
  2. 雙鏈表
    除了next指針之外,還有一個prev指針指向其上一個節(jié)點爷耀。
  • 按照有無循環(huán)分類
  1. 普通鏈表
    頭無prev甘桑,尾無next。
  1. 循環(huán)鏈表
    首尾相接的鏈表。
    最后一個節(jié)點的next指針指向其第一個節(jié)點
    對于雙鏈表跑杭,其第一個節(jié)點的prev指針指向最后一個節(jié)點铆帽。



鏈表問題代碼實現(xiàn)的關(guān)鍵點

  • 鏈表調(diào)整函數(shù)的返回值,往往是節(jié)點類型

鏈表在調(diào)整過程中往往遇到改變頭部的情況德谅,如果頭節(jié)點被改變則需要返回一個新頭部爹橱。

  • 在調(diào)整鏈表的過程中,先采用畫圖的方式理清邏輯

注意那些指針變化了窄做,同時注意對前后節(jié)點的影響愧驱。

  • 邊界條件的處理

頭節(jié)點,尾節(jié)點浸策,空節(jié)點的特殊處理冯键。


鏈表插入和刪除的注意事項

  • 特殊處理鏈表為空或長度為1
  • 插入過程的調(diào)整

取得前后節(jié)點,將前節(jié)點的next指向新節(jié)點庸汗,新節(jié)點的next指向后節(jié)點惫确。

  • 刪除過程的調(diào)整

取得前后節(jié)點,將前一個節(jié)點的next指針指向后一個節(jié)點蚯舱。

  • 頭尾插入或刪除

在邏輯的設(shè)計上應(yīng)該考慮空節(jié)點的情況


鏈表翻轉(zhuǎn)

  • 特殊處理鏈表為空或長度為1
  • 單鏈表的翻轉(zhuǎn)

已翻轉(zhuǎn)的頭節(jié)點head改化,下一個節(jié)點now

  1. 將now節(jié)點的next指向head
  2. 將now節(jié)點設(shè)置為已翻轉(zhuǎn)部分的新head

需要注意在執(zhí)行1,2步驟之前需要一個變量來儲存原now節(jié)點的next節(jié)點枉昏。
步驟2設(shè)置了新的head之后陈肛,將該節(jié)點作為新的now,繼續(xù)翻轉(zhuǎn)。


向一個有序的環(huán)境鏈表中插入一個節(jié)點蜡感,并保持依舊有序争占。

要求時間復(fù)雜度O(N),額外空間復(fù)雜度O(1)谈撒。

  • 如果鏈表為空

讓新節(jié)點node自己成為環(huán)形鏈表,并返回node即可匾南。

  • 如果鏈表不為空

令變量prve設(shè)為頭節(jié)點啃匿,current設(shè)為第二個節(jié)點,兩個節(jié)點同步移動蛆楞。

  • 當(dāng)有node<=prve && node>=current溯乒,則說明node應(yīng)該插入二者之間

  • 若prve回到head但依舊沒有合適的位置插入
    說明node為最大值或最小值,插入head之前即可豹爹。
    需要區(qū)分為兩種情況下是否出現(xiàn)新的head裆悄,并返回。


對于一個單鏈表臂聋,在不給定head的情況下刪除指定node光稼。要求時間復(fù)雜度O(1)

  • 如果node.next不為空崖技,也就是node不是尾節(jié)點

如果工程允許,可以將node.next的內(nèi)容copy到node節(jié)點上钟哥,變相的刪除了node節(jié)點的數(shù)據(jù)迎献。

  • 如果node是尾節(jié)點

無法刪除


給定一個鏈表,與一個數(shù)組num腻贰。要求實現(xiàn)荷蘭國旗

  • 將鏈表遍歷成數(shù)組吁恍,然后進(jìn)行荷蘭國旗排序,最后還原成鏈表播演。
  • 遍歷鏈表的過程中使用三個小鏈表冀瓦。小于,等于写烤,大于翼闽。最后將三個鏈表串聯(lián)。

給定兩個有序鏈表的head洲炊,打印共同部分

  • 有一個為空直接返回
  • 采用外排的方式感局,直到有一個為空則停止。

給定一個單鏈表的head暂衡,實現(xiàn)一個調(diào)整鏈表的函數(shù)询微,使得每K個節(jié)點之間逆序,如果最后不足K個狂巢,則不調(diào)整撑毛。

  1. 鏈表為空,長度<k或者k<2
    直接返回
  • 通過棧結(jié)構(gòu)唧领,實現(xiàn)逆序
  1. 需要保留上次逆序的最后一位元素藻雌,修改其next。
  2. 最后段不足k個斩个,直接不修改胯杭。值將上次逆序的最后一個元素next設(shè)置好。
  3. 第一組的第一個節(jié)點為頭節(jié)點萨驶。
  • 不使用棧結(jié)構(gòu)歉摧,手動逆序

判斷一個鏈表是否為回文結(jié)構(gòu)

  1. 將鏈表節(jié)點依次入棧艇肴,在彈出時與原鏈表依次比對腔呜。

  2. 使用快行指針,通過二倍速的方式遍歷再悼。依次將慢指針的節(jié)點壓入棧中核畴,當(dāng)快節(jié)點遍歷到末尾時,慢指針正好處于中間位置冲九。
    繼續(xù)移動慢指針谤草,并與棧中彈出的元素做對比跟束。(需要注意總量的奇偶)


  3. 將后半部分鏈表進(jìn)行逆序處理,從兩端同時進(jìn)行遍歷比對



判斷一個單鏈表是否有環(huán)丑孩,如有則返回入環(huán)節(jié)點冀宴。時間復(fù)雜度O(N),額外空間復(fù)雜度O(1)

  • 1. 如果鏈表有結(jié)尾温学,則說明無環(huán)
  • 2.1 如果不要求額外空間復(fù)雜度略贮,可以直接用哈希表比對。
  • 2.2 使用快行指針的方式

如果兩指針相遇則表示有環(huán)仗岖,此時將快指針改為1逃延,并從head重新同步移動,相遇處即為入環(huán)位置或者還有另一個證明轧拄。

  • 代碼
/// 獲取入環(huán)節(jié)點
///
/// - Parameter node: 頭節(jié)點
/// - Returns: 有環(huán)則返回入環(huán)節(jié)點揽祥,否則返回空
func getLoopNode(head : Node) -> Node {
    //鏈表長度為 0,1檩电,2 不可能成環(huán)
    if head==nil || head.next==nil || head.next.next==nil {
        return nil
    }
    
    var slowP = head //慢行指針
    var fastP = head //快行指針
    
    while slowP != fastP {
        if slowP.next==nil || fastP.next.next==nil {  //鏈表有結(jié)尾拄丰,不可能成環(huán)
            return nil
        }
        slowP = slowP.next
        fastP = fastP.next.next
    }//運行到這里說明兩指針相遇了
    
    
    //從head開始遍歷再次相交則為入環(huán)點
    fastP = head
    while fastP != slowP {
        slowP = slowP.next
        fastP = fastP.next
    }
    
    return fastP
}

兩個無環(huán)單鏈表是否相交,時間復(fù)雜度O(N+M)俐末,額外空間復(fù)雜度O(1)

  • 1. 先遍歷兩個鏈表確定長度
  • 2. 若兩個鏈表結(jié)尾不同愈案,則不相交
  • 3. 另長鏈表從短鏈表開始位置與短鏈表再次同步遍歷,查看是否相同鹅搪。
  • 代碼
/// 兩個無環(huán)單鏈表是否相交
///
/// - Parameters:
///   - head1: 鏈表1
///   - head2: 鏈表2
/// - Returns: 相交點或者為空
func noLoop(head1:Node ,head2:Node) -> Node {
    
    if head1==nil || head2==nil {
        return nil
    }
    var p1 = head1
    var p2 = head2
    
    //獲取兩個鏈表長度差值
    var n = 0
    while p1.next != nil {
        p1 = p1.next
        n+=1
    }
    while p2.next != nil {
        p2 = p2.next
        n-=1
    }
    
    if p1 != p2 { //若兩個鏈表結(jié)尾不同站绪,則一定不相交
        return nil
    }
    
    p1 = n>=0 ? head1:head2 //使 p1 指向較長的鏈表
    p2 = p2==head1 ? head2:head1 //使p2 指向另一個鏈表
    
    n = abs(n) //取絕對值
    while n>0 {//將長鏈表移動n次。
        p1 = p1.next
        n-=1
    }
    
    //查找鏈表上第一個相同的點
    while p1 != p2 {
        p1 = p1.next
        p2 = p2.next
    }
    
    return p1
}

判斷兩個有環(huán)單鏈表是否相交丽柿,時間復(fù)雜度O(N+M)恢准,額外空間復(fù)雜度O(1)

首先都需要先去定單獨的入環(huán)節(jié)點,然后

  1. 是否入環(huán)之前已經(jīng)相交


  2. 是否入環(huán)時才相交甫题,則入環(huán)位置節(jié)點相同



    如果相交點為loop1或者loop2馁筐,則為入環(huán)時才相交

  3. 入環(huán)后才相交
    循環(huán)其中一個環(huán),若遇到另一個的入環(huán)節(jié)點則返回坠非。


  4. 否則敏沉,兩鏈表并未相交

  • 代碼
/// 兩個有環(huán)單鏈表是否相交
///
/// - Parameters:
///   - head1: 鏈表1
///   - head2: 鏈表2
///   - loop1: 鏈表1入環(huán)點
///   - loop2: 鏈表2入環(huán)點
/// - Returns: 相交點或者為空
func bothLoop(head1:Node ,head2:Node ,loop1:Node ,loop2:Node) -> Node {
    
    if head1==nil || head2==nil{
        return nil
    }
    
    if loop1 == loop2 { //兩個鏈表在入環(huán)之前已經(jīng)相交
        var p1 = head1
        var p2 = head2
        
        //獲取兩個鏈表長度差值
        var n = 0
        while p1.next != loop1 {
            p1 = p1.next
            n+=1
        }
        while p2.next != loop2 {
            p2 = p2.next
            n-=1
        }
        
        p1 = n>=0 ? head1:head2 //使 p1 指向較長的鏈表
        p2 = p2==head1 ? head2:head1 //使p2 指向另一個鏈表
        
        n = abs(n) //取絕對值
        while n>0 {//將長鏈表移動n次。
            p1 = p1.next
            n-=1
        }
        
        //查找鏈表上第一個相同的點
        while p1 != p2 {
            p1 = p1.next
            p2 = p2.next
        }
        
        return p1
    }else { //兩個鏈表在入環(huán)之后才相交
        var p1 = loop1.next
        var p2 = loop2
        while p1 == loop1 { //循環(huán)loop1一次
            p1 = p1.next
            if p1 == p2 {
                return p1
            }
        }
        return nil  //并未相交
    }
}


判斷兩個鏈表是否相交炎码,并返回第一個相交的節(jié)點

  1. 嘗試找到各自的入環(huán)節(jié)點

  2. 若一個有環(huán)一個無環(huán)盟迟,則不相交

  3. 若都為無環(huán),則按照上文《兩個無環(huán)單鏈表是否相交》的方式查找

  4. 若都為有環(huán)潦闲,則按照上文《判斷兩個有環(huán)單鏈表是否相交》的方式查找

  • 代碼
func getIntersectNode(head1:Node,head2:Node) -> Node {
    if head1 == nil || head2 == nil {
        return nil
    }
    
    //獲取兩個入環(huán)節(jié)點
    var loop1 = getLoopNode(head: head1)
    var loop2 = getLoopNode(head: head2)
    
    if (loop1 == nil && loop2 != nil)||(loop1 != nil && loop2==nil) {
        return nil //一個有環(huán)一個無環(huán)攒菠,肯定不相交
    }
    
    if loop1==nil || loop2==nil {//兩個鏈表都不為環(huán)型結(jié)構(gòu)
        return noLoop(head1: head1, head2: head2)
    }else { //兩個環(huán)形鏈表
        return bothLoop(head1: head1, head2: head2, loop1: loop1, loop2: loop2)
    }
}


參考資料

左神牛課網(wǎng)算法課

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市歉闰,隨后出現(xiàn)的幾起案子辖众,更是在濱河造成了極大的恐慌卓起,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,907評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件凹炸,死亡現(xiàn)場離奇詭異戏阅,居然都是意外死亡,警方通過查閱死者的電腦和手機啤它,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,987評論 3 395
  • 文/潘曉璐 我一進(jìn)店門饲握,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蚕键,你說我怎么就攤上這事救欧。” “怎么了锣光?”我有些...
    開封第一講書人閱讀 164,298評論 0 354
  • 文/不壞的土叔 我叫張陵笆怠,是天一觀的道長。 經(jīng)常有香客問我誊爹,道長蹬刷,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,586評論 1 293
  • 正文 為了忘掉前任频丘,我火速辦了婚禮办成,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘搂漠。我一直安慰自己迂卢,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,633評論 6 392
  • 文/花漫 我一把揭開白布桐汤。 她就那樣靜靜地躺著而克,像睡著了一般。 火紅的嫁衣襯著肌膚如雪怔毛。 梳的紋絲不亂的頭發(fā)上员萍,一...
    開封第一講書人閱讀 51,488評論 1 302
  • 那天,我揣著相機與錄音拣度,去河邊找鬼碎绎。 笑死,一個胖子當(dāng)著我的面吹牛抗果,可吹牛的內(nèi)容都是我干的筋帖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,275評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼窖张,長吁一口氣:“原來是場噩夢啊……” “哼幕随!你這毒婦竟也來了蚁滋?” 一聲冷哼從身側(cè)響起宿接,我...
    開封第一講書人閱讀 39,176評論 0 276
  • 序言:老撾萬榮一對情侶失蹤赘淮,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后睦霎,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梢卸,經(jīng)...
    沈念sama閱讀 45,619評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,819評論 3 336
  • 正文 我和宋清朗相戀三年副女,在試婚紗的時候發(fā)現(xiàn)自己被綠了蛤高。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,932評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡碑幅,死狀恐怖戴陡,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情沟涨,我是刑警寧澤恤批,帶...
    沈念sama閱讀 35,655評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站裹赴,受9級特大地震影響喜庞,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜棋返,卻給世界環(huán)境...
    茶點故事閱讀 41,265評論 3 329
  • 文/蒙蒙 一延都、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧睛竣,春花似錦晰房、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,871評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至躏惋,卻和暖如春幽污,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背簿姨。 一陣腳步聲響...
    開封第一講書人閱讀 32,994評論 1 269
  • 我被黑心中介騙來泰國打工距误, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人扁位。 一個月前我還...
    沈念sama閱讀 48,095評論 3 370
  • 正文 我出身青樓准潭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親域仇。 傳聞我的和親對象是個殘疾皇子刑然,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,884評論 2 354