劍指Offer[24]:反轉鏈表

題目

  定義一個函數,輸入一個鏈表的頭節點,反轉該鏈表並輸出反轉後鏈表的頭節點。我們將鏈表的節點定義如下:

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;
}

迭代解法的時間複雜度爲 O(n)O(n),空間複雜度爲 O(1)O(1)

遞歸解法

  遞歸解法理解起來稍微複雜一點。假設鏈表從第 k+1k+1 個元素開始的後半部分已經被反轉,當前節點爲 nkn_k,也就是當前鏈表如下:

n1nk1nknk+1nk+2nmn_{1} \rightarrow \ldots \rightarrow n_{k-1} \rightarrow n_{k} \rightarrow n_{k+1} \leftarrow n_{k+2} \leftarrow \ldots \leftarrow n_{m}

那麼接下來要做的就是讓 nk+1n_{k+1} 的 下一個節點爲nkn_k,另外就是每次的中間節點(上面的 nk+1n_{k+1})是不指向其餘節點的。

遞歸解法的代碼如下:

// 遞歸解法
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;
} 

相比起迭代解法,遞歸法的代碼短小精悍,其時間複雜度爲 O(n)O(n),由於遞歸使用了額外棧空間,因此遞歸法的空間複雜度爲 O(n)O(n)

擴展——反轉部分鏈表

  LeetCode的第92題是反轉鏈表的進階版——反轉鏈表的一部分,題目如下:

反轉從位置 mn 的鏈表。請使用一趟掃描完成反轉。1 ≤ mn ≤ 鏈表長度。

以鏈表 1 -> 2 -> 3 -> 4 -> 5 -> 6 爲例,假如要反轉的部分是 2 到 5,反轉之後的鏈表應當爲 1 ->5 -> 4 -> 3 -> 2 -> 6 。反轉過程通過下圖來說明:

  首先通過計數找到反轉的起始節點標記爲 curr,並記錄其前面節點 prev和後一個節點 temp,如圖中第1行所示;接下來分三步:

  1. curr.next指向 temp.next
  2. temp.next指向 prev.next
  3. 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,結束位置爲鏈表長度就可以實現整個列表的反轉,因此上面的程序能作爲一個更一般化的反轉鏈表的程序。其時間複雜度爲 O(n)O(n),空間複雜度爲 O(1)O(1)

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