題目
定義一個函數,輸入一個鏈表的頭節點,反轉該鏈表並輸出反轉後鏈表的頭節點。我們將鏈表的節點定義如下:
class ListNode{
private int val; // 節點值
private ListNode next; // 下一個節點
}
LeetCode中也有相應的題目
解法
迭代解法
迭代的方法很好理解:爲了反轉鏈表我們從前向後遍歷鏈表,改變每一個節點的指針指向,讓每個節點都指向前一個節點,最後返回尾節點。
迭代方法主要注意兩個問題:第一個是頭節點反轉之後是尾節點,因此頭節點應當指向null
;第二個要注意的點是,每次讓當前節點指向前一個結點後,會將鏈表截斷爲兩部分,因此每次需要保存下一個節點。
迭代解法的代碼如下:
// 迭代法
public static ListNode reverse(ListNode head){
ListNode curr = head; // 當前節點
ListNode prev = null; // 當前節點的前一個節點
while(curr != null){
ListNode temp = curr.next; // 臨時指針指向當前節點的下一個節點
curr.next = prev; // 當前節點指向前一個節點
prev = curr; // 指針後移
curr = temp;
}
return prev;
}
迭代解法的時間複雜度爲 ,空間複雜度爲 。
遞歸解法
遞歸解法理解起來稍微複雜一點。假設鏈表從第 個元素開始的後半部分已經被反轉,當前節點爲 ,也就是當前鏈表如下:
那麼接下來要做的就是讓 的 下一個節點爲,另外就是每次的中間節點(上面的 )是不指向其餘節點的。
遞歸解法的代碼如下:
// 遞歸解法
public static ListNode reverse(ListNode head){
if(head == null || head.next == null) return head;
ListNode p = reverse(head.next);
head.next.next = head;
head.next = null;
return p;
}
相比起迭代解法,遞歸法的代碼短小精悍,其時間複雜度爲 ,由於遞歸使用了額外棧空間,因此遞歸法的空間複雜度爲 。
擴展——反轉部分鏈表
LeetCode的第92題是反轉鏈表的進階版——反轉鏈表的一部分,題目如下:
反轉從位置 m 到 n 的鏈表。請使用一趟掃描完成反轉。1 ≤ m ≤ n ≤ 鏈表長度。
以鏈表 1 -> 2 -> 3 -> 4 -> 5 -> 6 爲例,假如要反轉的部分是 2 到 5,反轉之後的鏈表應當爲 1 ->5 -> 4 -> 3 -> 2 -> 6 。反轉過程通過下圖來說明:
首先通過計數找到反轉的起始節點標記爲 curr
,並記錄其前面節點 prev
和後一個節點 temp
,如圖中第1行所示;接下來分三步:
- 讓
curr.next
指向temp.next
; - 讓
temp.next
指向prev.next
; - 讓
prev.next
指向temp
。
執行完上面三步後會得到圖中第2行的結構,接下來重複上面的三步直到 curr
到達反轉部分的末尾,反轉完成。代碼如下:
// 反轉鏈表的一部分
public static ListNode reverseBetween(ListNode head, int start, int end){
if(start == end) return head;
ListNode curr = head, newHead = new ListNode(-1);
ListNode prev = newHead;
newHead.next = head;
int count = 1;
while(count < start){
prev = prev.next;
count++;
}
curr = prev.next; // 找到反轉的起始節點
while(count++ < end){
ListNode temp = curr.next; // 保存當前節點的下一個節點
curr.next = curr.next.next;
temp.next = prev.next;
prev.next = temp;
}
return newHead.next;
}
如果提前已知鏈表的長度,那麼讓起始位置爲1,結束位置爲鏈表長度就可以實現整個列表的反轉,因此上面的程序能作爲一個更一般化的反轉鏈表的程序。其時間複雜度爲 ,空間複雜度爲 。