數據結構基礎--鏈表

目錄

  • 基本性質
  • 鏈表的分類
    • 按連接方向分類
    • 按照有無循環分類
  • 鏈表問題代碼實現的關鍵點
  • 鏈表插入和刪除的注意事項
  • 鏈表翻轉
  • 向一個有序的環境鏈表中插入一個節點,並保持依舊有序
  • 對於一個單鏈表,在不給定head的情況下刪除指定node。要求時間複雜度O(1)
  • 給定一個鏈表,與一個數組num。要求實現荷蘭國旗
  • 給定兩個有序鏈表的head,打印共同部分
  • 給定一個單鏈表的head,實現一個調整鏈表的函數,使得每K個節點之間逆序,如果最後不足K個,則不調整
  • 判斷一個鏈表是否爲迴文結構
  • 判斷一個單鏈表是否有環,如有則返回入環節點。時間複雜度O(N),額外空間複雜度O(1)
  • 兩個無環單鏈表是否相交,時間複雜度O(N+M),額外空間複雜度O(1)
  • 判斷兩個有環單鏈表是否相交,時間複雜度O(N+M),額外空間複雜度O(1)
  • 判斷兩個鏈表是否相交,並返回第一個相交的節點

基本性質

  • 鏈表問題算法難度不高,但考察代碼的實現能力
  • 鏈表和數組一樣,都是一種線性結構
  1. 數組是物理地址上一段連續的存儲空間。
    可以通過下標直接獲取元素
    當內容超出容量時需要重新定義數組。
  2. 鏈表空間不一定保持聯繫,爲臨時分配的。
    只能從鏈表的頭部開始一個一個查找
    增刪的效率高於數組,因爲不需要更改內存結構

鏈表的分類

  • 按連接方向分類
  1. 單鏈表
    每個節點只能通過next指針,指向下一個節點。
  2. 雙鏈表
    除了next指針之外,還有一個prev指針指向其上一個節點。
  • 按照有無循環分類
  1. 普通鏈表
    頭無prev,尾無next。
  1. 循環鏈表
    首尾相接的鏈表。
    最後一個節點的next指針指向其第一個節點
    對於雙鏈表,其第一個節點的prev指針指向最後一個節點。



鏈表問題代碼實現的關鍵點

  • 鏈表調整函數的返回值,往往是節點類型

鏈表在調整過程中往往遇到改變頭部的情況,如果頭節點被改變則需要返回一個新頭部。

  • 在調整鏈表的過程中,先採用畫圖的方式理清邏輯

注意那些指針變化了,同時注意對前後節點的影響。

  • 邊界條件的處理

頭節點,尾節點,空節點的特殊處理。


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

  • 特殊處理鏈表爲空或長度爲1
  • 插入過程的調整

取得前後節點,將前節點的next指向新節點,新節點的next指向後節點。

  • 刪除過程的調整

取得前後節點,將前一個節點的next指針指向後一個節點。

  • 頭尾插入或刪除

在邏輯的設計上應該考慮空節點的情況


鏈表翻轉

  • 特殊處理鏈表爲空或長度爲1
  • 單鏈表的翻轉

已翻轉的頭節點head,下一個節點now

  1. 將now節點的next指向head
  2. 將now節點設置爲已翻轉部分的新head

需要注意在執行1,2步驟之前需要一個變量來儲存原now節點的next節點。
步驟2設置了新的head之後,將該節點作爲新的now,繼續翻轉。


向一個有序的環境鏈表中插入一個節點,並保持依舊有序。

要求時間複雜度O(N),額外空間複雜度O(1)。

  • 如果鏈表爲空

讓新節點node自己成爲環形鏈表,並返回node即可。

  • 如果鏈表不爲空

令變量prve設爲頭節點,current設爲第二個節點,兩個節點同步移動。

  • 當有node<=prve && node>=current,則說明node應該插入二者之間

  • 若prve回到head但依舊沒有合適的位置插入
    說明node爲最大值或最小值,插入head之前即可。
    需要區分爲兩種情況下是否出現新的head,並返回。


對於一個單鏈表,在不給定head的情況下刪除指定node。要求時間複雜度O(1)

  • 如果node.next不爲空,也就是node不是尾節點

如果工程允許,可以將node.next的內容copy到node節點上,變相的刪除了node節點的數據。

  • 如果node是尾節點

無法刪除


給定一個鏈表,與一個數組num。要求實現荷蘭國旗

  • 將鏈表遍歷成數組,然後進行荷蘭國旗排序,最後還原成鏈表。
  • 遍歷鏈表的過程中使用三個小鏈表。小於,等於,大於。最後將三個鏈表串聯。

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

  • 有一個爲空直接返回
  • 採用外排的方式,直到有一個爲空則停止。

給定一個單鏈表的head,實現一個調整鏈表的函數,使得每K個節點之間逆序,如果最後不足K個,則不調整。

  1. 鏈表爲空,長度<k或者k<2
    直接返回
  • 通過棧結構,實現逆序
  1. 需要保留上次逆序的最後一位元素,修改其next。
  2. 最後段不足k個,直接不修改。值將上次逆序的最後一個元素next設置好。
  3. 第一組的第一個節點爲頭節點。
  • 不使用棧結構,手動逆序

判斷一個鏈表是否爲迴文結構

  1. 將鏈表節點依次入棧,在彈出時與原鏈表依次比對。

  2. 使用快行指針,通過二倍速的方式遍歷。依次將慢指針的節點壓入棧中,當快節點遍歷到末尾時,慢指針正好處於中間位置。
    繼續移動慢指針,並與棧中彈出的元素做對比。(需要注意總量的奇偶)


  3. 將後半部分鏈表進行逆序處理,從兩端同時進行遍歷比對



判斷一個單鏈表是否有環,如有則返回入環節點。時間複雜度O(N),額外空間複雜度O(1)

  • 1. 如果鏈表有結尾,則說明無環
  • 2.1 如果不要求額外空間複雜度,可以直接用哈希表比對。
  • 2.2 使用快行指針的方式

如果兩指針相遇則表示有環,此時將快指針改爲1,並從head重新同步移動,相遇處即爲入環位置或者還有另一個證明

  • 代碼
/// 獲取入環節點
///
/// - Parameter node: 頭節點
/// - Returns: 有環則返回入環節點,否則返回空
func getLoopNode(head : Node) -> Node {
    //鏈表長度爲 0,1,2 不可能成環
    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 {  //鏈表有結尾,不可能成環
            return nil
        }
        slowP = slowP.next
        fastP = fastP.next.next
    }//運行到這裏說明兩指針相遇了
    
    
    //從head開始遍歷再次相交則爲入環點
    fastP = head
    while fastP != slowP {
        slowP = slowP.next
        fastP = fastP.next
    }
    
    return fastP
}

兩個無環單鏈表是否相交,時間複雜度O(N+M),額外空間複雜度O(1)

  • 1. 先遍歷兩個鏈表確定長度
  • 2. 若兩個鏈表結尾不同,則不相交
  • 3. 另長鏈表從短鏈表開始位置與短鏈表再次同步遍歷,查看是否相同。
  • 代碼
/// 兩個無環單鏈表是否相交
///
/// - 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 { //若兩個鏈表結尾不同,則一定不相交
        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
}

判斷兩個有環單鏈表是否相交,時間複雜度O(N+M),額外空間複雜度O(1)

首先都需要先去定單獨的入環節點,然後

  1. 是否入環之前已經相交


  2. 是否入環時才相交,則入環位置節點相同



    如果相交點爲loop1或者loop2,則爲入環時才相交

  3. 入環後才相交
    循環其中一個環,若遇到另一個的入環節點則返回。


  4. 否則,兩鏈表並未相交

  • 代碼
/// 兩個有環單鏈表是否相交
///
/// - Parameters:
///   - head1: 鏈表1
///   - head2: 鏈表2
///   - loop1: 鏈表1入環點
///   - loop2: 鏈表2入環點
/// - Returns: 相交點或者爲空
func bothLoop(head1:Node ,head2:Node ,loop1:Node ,loop2:Node) -> Node {
    
    if head1==nil || head2==nil{
        return nil
    }
    
    if loop1 == loop2 { //兩個鏈表在入環之前已經相交
        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 { //兩個鏈表在入環之後才相交
        var p1 = loop1.next
        var p2 = loop2
        while p1 == loop1 { //循環loop1一次
            p1 = p1.next
            if p1 == p2 {
                return p1
            }
        }
        return nil  //並未相交
    }
}


判斷兩個鏈表是否相交,並返回第一個相交的節點

  1. 嘗試找到各自的入環節點

  2. 若一個有環一個無環,則不相交

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

  4. 若都爲有環,則按照上文《判斷兩個有環單鏈表是否相交》的方式查找

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


參考資料

左神牛課網算法課

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章