題解:反轉連結串列
這道題目要求我們將一個單連結串列進行反轉,返回反轉後的連結串列。連結串列的反轉可以透過 迭代 和 遞迴 兩種方法來實現。下面我們將詳細解釋這兩種方法,並透過例子演示每一步的變化過程。
方法一:迭代法
思路:
- 我們用三個指標來完成連結串列的反轉:
prev
表示前一個節點,curr
表示當前節點,next
表示下一個節點。 - 透過不斷將當前節點的
next
指標指向prev
,實現連結串列的逐步反轉。
迭代的步驟:
- 初始化
prev = null
和curr = head
,然後開始遍歷連結串列。 - 在每次迭代中,先用
next
儲存curr.next
,避免連結串列斷開。 - 將
curr.next
指向prev
,反轉當前節點的指向。 - 將
prev
移動到curr
,然後將curr
移動到next
,繼續下一次迭代。 - 當
curr
為null
時,連結串列反轉完成,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]
-
初始狀態:
prev = null curr = 1 -> 2 -> 3 -> 4 -> 5
-
第一步:
next = 2 -> 3 -> 4 -> 5 curr.next = prev // 1 -> null prev = 1 -> null curr = 2 -> 3 -> 4 -> 5
-
第二步:
next = 3 -> 4 -> 5 curr.next = prev // 2 -> 1 -> null prev = 2 -> 1 -> null curr = 3 -> 4 -> 5
-
第三步:
next = 4 -> 5 curr.next = prev // 3 -> 2 -> 1 -> null prev = 3 -> 2 -> 1 -> null curr = 4 -> 5
-
第四步:
next = 5 curr.next = prev // 4 -> 3 -> 2 -> 1 -> null prev = 4 -> 3 -> 2 -> 1 -> null curr = 5
-
第五步:
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
置為空,完成反轉。
遞迴步驟:
- 如果
head
為null
或者head.next
為null
,直接返回head
作為新的頭節點(即遞迴的終止條件)。 - 遞迴反轉剩餘的連結串列。
- 將當前節點的下一個節點的
next
指向自己,同時將自己的next
置為空。 - 返回新的頭節點。
程式碼實現:
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]
-
初始遞迴:
reverseList(1)
遞迴呼叫reverseList(2)
。
-
遞迴到最後一個節點:
reverseList(5)
返回5
作為新頭節點。
-
逐步反轉連結串列:
-
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) 的額外空間。