每日演算法隨筆:反轉連結串列

鱼摆摆不摆發表於2024-09-10

題解:反轉連結串列

這道題目要求我們將一個單連結串列進行反轉,返回反轉後的連結串列。連結串列的反轉可以透過 迭代遞迴 兩種方法來實現。下面我們將詳細解釋這兩種方法,並透過例子演示每一步的變化過程。

方法一:迭代法

思路

  • 我們用三個指標來完成連結串列的反轉:prev 表示前一個節點,curr 表示當前節點,next 表示下一個節點。
  • 透過不斷將當前節點的 next 指標指向 prev,實現連結串列的逐步反轉。

迭代的步驟

  1. 初始化 prev = nullcurr = head,然後開始遍歷連結串列。
  2. 在每次迭代中,先用 next 儲存 curr.next,避免連結串列斷開。
  3. curr.next 指向 prev,反轉當前節點的指向。
  4. prev 移動到 curr,然後將 curr 移動到 next,繼續下一次迭代。
  5. currnull 時,連結串列反轉完成,prev 就是新的頭節點。

程式碼實現

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next; // 儲存下一個節點
            curr.next = prev; // 反轉當前節點的指向
            prev = curr; // 將 prev 前移
            curr = next; // 將 curr 前移
        }
        return prev; // prev 成為新連結串列的頭節點
    }
}

例子演示

輸入head = [1,2,3,4,5]

  1. 初始狀態:

    prev = null
    curr = 1 -> 2 -> 3 -> 4 -> 5
    
  2. 第一步:

    next = 2 -> 3 -> 4 -> 5
    curr.next = prev  // 1 -> null
    prev = 1 -> null
    curr = 2 -> 3 -> 4 -> 5
    
  3. 第二步:

    next = 3 -> 4 -> 5
    curr.next = prev  // 2 -> 1 -> null
    prev = 2 -> 1 -> null
    curr = 3 -> 4 -> 5
    
  4. 第三步:

    next = 4 -> 5
    curr.next = prev  // 3 -> 2 -> 1 -> null
    prev = 3 -> 2 -> 1 -> null
    curr = 4 -> 5
    
  5. 第四步:

    next = 5
    curr.next = prev  // 4 -> 3 -> 2 -> 1 -> null
    prev = 4 -> 3 -> 2 -> 1 -> null
    curr = 5
    
  6. 第五步:

    next = null
    curr.next = prev  // 5 -> 4 -> 3 -> 2 -> 1 -> null
    prev = 5 -> 4 -> 3 -> 2 -> 1 -> null
    curr = null
    

輸出[5, 4, 3, 2, 1]

複雜度分析

  • 時間複雜度:O(n),其中 n 是連結串列的節點數。我們只遍歷了連結串列一次。
  • 空間複雜度:O(1),只用了常數級別的額外空間。

方法二:遞迴法

思路

  • 我們遞迴地反轉連結串列的後續部分,直到最後一個節點成為新的頭節點。
  • 每次遞迴返回時,將當前節點的下一個節點的 next 指向自己,同時將自己的 next 置為空,完成反轉。

遞迴步驟

  1. 如果 headnull 或者 head.nextnull,直接返回 head 作為新的頭節點(即遞迴的終止條件)。
  2. 遞迴反轉剩餘的連結串列。
  3. 將當前節點的下一個節點的 next 指向自己,同時將自己的 next 置為空。
  4. 返回新的頭節點。

程式碼實現

class Solution {
    public ListNode reverseList(ListNode head) {
        if (head == null || head.next == null) return head; // 遞迴終止條件
        ListNode newHead = reverseList(head.next); // 反轉後續連結串列
        head.next.next = head; // 將後面的節點指向自己
        head.next = null; // 斷開當前節點與後續的連線
        return newHead; // 返回新的頭節點
    }
}

例子演示

輸入head = [1,2,3,4,5]

  1. 初始遞迴:

    • reverseList(1) 遞迴呼叫 reverseList(2)
  2. 遞迴到最後一個節點:

    • reverseList(5) 返回 5 作為新頭節點。
  3. 逐步反轉連結串列:

    • reverseList(4)

      head = 4 -> 5
      反轉後 5 -> 4
      head.next.next = 4
      head.next = null // 4 -> null
      返回 5 -> 4 -> null
      
    • reverseList(3)

      head = 3 -> 4 -> null
      反轉後 5 -> 4 -> 3
      head.next.next = 3
      head.next = null
      返回 5 -> 4 -> 3 -> null
      
    • reverseList(2)

      head = 2 -> 3 -> null
      反轉後 5 -> 4 -> 3 -> 2
      head.next.next = 2
      head.next = null
      返回 5 -> 4 -> 3 -> 2 -> null
      
    • reverseList(1)

      head = 1 -> 2 -> null
      反轉後 5 -> 4 -> 3 -> 2 -> 1
      head.next.next = 1
      head.next = null
      返回 5 -> 4 -> 3 -> 2 -> 1 -> null
      

輸出[5, 4, 3, 2, 1]

複雜度分析

  • 時間複雜度:O(n),其中 n 是連結串列的節點數。遞迴過程中每個節點只處理一次。
  • 空間複雜度:O(n),遞迴呼叫的棧深度為 n

總結:

  • 迭代法:透過三個指標逐步反轉連結串列,時間和空間複雜度都為 O(n) 和 O(1),適合在空間要求較嚴格的場景下使用。
  • 遞迴法:利用函式呼叫棧進行遞迴,程式碼簡潔直觀,但需要 O(n) 的額外空間。

相關文章