鏈表高頻面試題(包括反轉、合併、相交、分割、環長等)

  1. 整個鏈表翻轉

https://leetcode-cn.com/problems/reverse-linked-list/

1.1 題目描述

反轉一個單鏈表。

示例:

進階: 你可以迭代或遞歸地反轉鏈表。你能否用兩種方法解決這道題?

1.2 算法實現

1.2.1 算法思路

雙指針:一個指針 pre 指向原鏈表當前節點的前一個節點,另一個指針 next 暫存當前節點的下一個節點。

依次遍歷鏈表的各個節點,每遍歷一個節點即將其指向前一個節點(倒置),主要分 4 步:

  1. 先備份後一個節點(以防移動指針的時候找不到下一個節點):next = head.Next;

  2. 後一個節點的 Next 指向前一個節點:next.Next = pre;

  3. 前置節點後移一個位置到當前節點:pre = head;

  4. 後置節點後移一個位置到下一個節點:head = next,注意這一步(千萬不要)容易忽略。

1.2.2 代碼實現

type LikedList struct{
    Val int
    Next *LikedList
}

// 頭插法,倒置單鏈表
func reverseLikedList(head *LikedList)*LikedList{
    if(head == nil || head.Next == nil){
        return head
    }
 var pre, next *LikedList 
 // head是當前節點,pre是當前節點的前一個幾點,next是當前節點的後一個節點
    for head != nil {  // 當前節點不爲空
        next = head.Next    // 備份head.Next(指向當前節點的下一個節點),防止移動節點時找不到後置節點
  head.Next = pre     // 更新head.Next,後一個節點指向前一個(翻轉)
  pre = head          // 前一個節點後移
  head = next   // 當前節點後移
 }
    return pre              // 注意:當pre指向原鏈表的最後一個節點時,head已經指向最後一個節點的Next即空節點,所以這裏應返回pre
}
  1. 反轉鏈表中的第 m 至第 n 個節點

題目來源:

https://leetcode-cn.com/problems/reverse-linked-list-ii

2.1 題目描述

反轉從位置 m 到 n 的鏈表。請使用一趟掃描完成反轉。

說明:

1 ≤ m ≤ n ≤ 鏈表長度。

示例:

2.2 算法實現

2.2.1 算法思路

有四個關鍵的位置需要記錄:第 m 個節點及其前驅節點、第 n 個節點及其後繼節點。

  1. 對整個鏈表遍歷,將鏈表 head 的頭指針向後移動 m-1 個節點,即爲第 m 個節點,記其前驅節點爲 pre;

  2. 自第 m 個節點開始逆置 changLen = n-m+1 個節點,即將第 m 至第 n 個節點依次反轉,並記錄下來反轉後的頭結點 reversedListHead 和尾節點 reversedListTail;

  3. 將反轉後的尾節點 reversedListTail 的 Next 域與第 n 個節點的後繼節點 head 連接,pre 節點的 Next 域與 reversedListHead 連接。

2.2.2 代碼實現

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func reverseBetween(head *ListNode, m int, n int) *ListNode {
    changeLen := n - m + 1                      // 計算需要逆置的節點個數
    var pre *ListNode = nil                     // 初始化開始逆置的節點的前驅
    var result *ListNode = head                 // 最終轉換後的鏈表的頭結點,非特殊情況即爲head
    move := m - 1                               // head向後移動m-1個位置指向第m個節點
    for(head != nil && move > 0){               // 將head後移m-1個位置,即
        pre = head                              // for循環後pre指向第m個節點的前驅節點
        head = head.Next                        // for循環後head指向第m個節點
        move--
    }

    var reversedListTail *ListNode = head       // 此時reversedListTail指向的是第m個節點,該節點即是鏈表片段反轉後的尾節點
    var reversedListHead *ListNode = nil        // 記錄鏈表片段反轉後的頭結點
    for(head != nil && changeLen > 0){          // 逆置changeLen個節點
        next := head.Next                       // 暫存當前節點的下一個節點
        head.Next = reversedListHead            // 當前節點的Next指針域指向新開闢的反轉後的頭結點
        reversedListHead = head                 // 反轉後的鏈表的頭結點後移一個位置
        head = next                             // 當前節點後移一個節點
        changeLen--                             // 每完成一個節點逆置,changLen--
    }

    reversedListTail.Next = head                // 連接逆置後的鏈表尾與逆置段的後一個節點,翻轉後的尾節點指向第n個節點的下一個節點
    if(pre != nil){                             // 如果pre不爲空,說明不是從第1個節點開始逆置的,即m > 1 
        pre.Next = reversedListHead             // 將逆置鏈表開始的節點前驅與逆置後的頭結點連接起來
    }else{                                      // 如果pre爲空,說明m=1,即從第1個節點開始逆置,結果即爲逆置後的頭結點
        result = reversedListHead
    }
    return result
}
  1. 求兩個鏈表之間的交點

3.1 題目描述

題目來源:

https://leetcode-cn.com/problems/intersection-of-two-linked-lists

編寫一個程序,找到兩個單鏈表相交的起始節點。

如下面的兩個鏈表:

在節點 c1 開始相交。

示例 1:

輸入解釋:相交節點的值爲 8 (注意,如果兩個列表相交則不能爲 0)。從各自的表頭開始算起,鏈表 A 爲 [4,1,8,4,5],鏈表 B 爲 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

推薦:250 期面試題彙總

示例 2:

輸入解釋:相交節點的值爲 2 (注意,如果兩個列表相交則不能爲 0)。從各自的表頭開始算起,鏈表 A 爲 [0,9,1,2,4],鏈表 B 爲 [3,2,4]。在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。

示例 3:

輸入解釋:從各自的表頭開始算起,鏈表 A 爲 [2,6,4],鏈表 B 爲 [1,5]。由於這兩個鏈表不相交,所以 intersectVal 必須爲 0,而 skipA 和 skipB 可以是任意值。解釋:這兩個鏈表不相交,因此返回 null。

注意:

3.2 算法實現

3.2.1 算法思路

  1. 對兩個鏈表 ListNode1 和 ListNode2 分別遍歷,計算出其長度 len1 和 len2;

  2. 長鏈表的頭指針後移 abs(len1 - len2) 個節點;

  3. 當鏈表未遍歷到頭,兩鏈表指向同一個節點時,該節點即爲兩鏈表的交點,沒有指向同一個節點則兩個頭結點指針繼續後移;遍歷完還沒有找到,則說明沒有交點,返回 nil。

3.2.2 代碼實現

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func getIntersectionNode(headA, headB *ListNode) *ListNode {
    var lenA, lenB int                                      // 分別記錄鏈表headA和鏈表headB的長度    
    lenA = getListLength(headA)
    lenB = getListLength(headB)

    if lenA > lenB {
        headA = forwardDeltaLenNode(lenA, lenB, headA)      // headA鏈表的頭結點指向移動多出的節點個位置後
    }else{
        headB = forwardDeltaLenNode(lenB, lenA, headB)      // 如果鏈表headB長,移動其頭結點到相應位置
    }
    for headA != nil && headB != nil {      // 沒有到達鏈表末尾
        if(headA == headB){                 // 當前兩個鏈表的節點相同,即指向同一個節點,說明找到的交點;否則,兩鏈表同時後移
            return headA
        }
        headA = headA.Next                  
        headB = headB.Next
    }
    return nil                              // 遍歷到鏈表末尾還沒有找到同一個節點,說明兩鏈表沒有交點
}

// 逐個節點遍歷,獲取鏈表長度
func getListLength(head *ListNode)int{
    var lengthList int 
    for head != nil {
        lengthList++
        head = head.Next
    }
    return lengthList
}

// 較長鏈表移動 長鏈表長度-短鏈表長度 個節點後對應的頭指針
// 參數longLen和shortLen分別表示長鏈表和短鏈表的長度,longList對應長鏈表
func forwardDeltaLenNode(longLen, shortLen int, longList *ListNode)*ListNode{
    deltaLen := longLen - shortLen
    for longList != nil && deltaLen != 0{
        longList = longList.Next
        deltaLen--
    }
    return longList             // 如果longList爲nil或者deltaLen=0直接返回此時的頭結點longList
}
  1. 判斷鏈表是否有環;如果有環,給出環所在的起始節點及環的長度。

4.1 判斷鏈表是否有環

4.2 給出有環鏈表的環的起始節點及長度

  1. 將鏈表換分爲以某個值 x 爲界限的前後兩部分

鏈表劃分依據:鏈表前面的部門小於 x,後面部分大於等於 x,且原有節點的相對順序不變。如:將 1->3-7->2->5->3->6-9-2 劃分爲以 5 爲界限的前後兩部分:1->3->2->3->2 ->7->5->6->9

5.1 問題描述

給定一個鏈表和一個特定值 x,對鏈表進行分隔,使得所有小於 x 的節點都在大於或等於 x 的節點之前。

你應當保留兩個分區中每個節點的初始相對位置。

示例:

鏈接:

https://leetcode-cn.com/problems/partition-list

5.2 算法實現

5.2.1 算法思路

鏈表分爲前後兩個部分,可以先依次遍歷鏈表,將小於 x 的節點依次插入到 preHead 指向的前半部分鏈表後面,將大於等於 x 的節點連接到 postHead 對應的另一個鏈表中;鏈表遍歷完將 preHead 的尾節點的 Next 域指向 postHead 結點的下一個節點,postHead 的尾節點的 Next 域置爲空,即實現了前後兩個鏈表的連接。

爲了連接 preHead 和 postHead 兩個鏈表,需要分別知道兩個鏈表的尾部,這裏使用 prePtr 和 postPtr 指針分別對 preHead 和 postHead 進行遍歷,知道兩個鏈表的尾節點。

5.2.2 代碼實現

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func partition(head *ListNode, x int) *ListNode {
    //var preHead, postHead *ListNode     // 前後兩部分鏈表的頭結點,不能這麼聲明,這樣聲明的話兩者都爲&ListNode{0, nil}其中Next域爲nil,當運行到prePtr.Next = head時會報panic: runtime error:invalid memory address or nil pointer deference錯誤
    preHead := &ListNode{0, nil}        // 生成一個preHead鏈表的頭結點,該節點固定一直不對其移動,改頭節點的Val爲多少都不影響結果,因爲使用的是頭結點的Next節點
    postHead := &ListNode{0, nil}       // 生成一個postHead鏈表的頭結點,該節點固定不動
    prePtr := preHead                   // prePtr指針對preHead鏈表進行遍歷,注意:開始時其指向preHead
    postPtr := postHead
    for head != nil {                   // 遍歷原鏈表
        if(head.Val < x ){              // 小於x的節點尾插到preHead鏈表後面
            prePtr.Next = head          // prePtr的Next域指向當前節點,即將當前節點從preHead鏈表尾部prePtr依次插入
            prePtr = head               // prePtr指針後移一位,指向當前節點
        }else{                          // 大於等於x的節點放在postHead鏈表的後面
            postPtr.Next = head         // 將當前節點插入到postHead鏈表尾節點postPtr後面
            postPtr = head              // postHead鏈表的尾節點後移一位
        }
        head = head.Next                // 當前指針後移一位
    }
    // 對head鏈表遍歷結束分爲preHead和postHead兩個子鏈表
    prePtr.Next = postHead.Next         // 注意:這裏是將preHead鏈表的尾節點prePtr的Next域指向postHead鏈表頭結點的下一個節點
    postPtr.Next = nil                  // postPtr的尾節點的Next域指向空
    return preHead.Next                 // 返回preHead鏈表的Next之後的鏈表
}

來源:gonline.blog.csdn.net/article/details/104187852

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/tfHv11RNwjHDlqAsjGeD9w