1. 程式人生 > 實用技巧 >連結串列反轉總結

連結串列反轉總結

1. 反轉整個連結串列

遞迴反轉連結串列

比如現在有這樣一個連結串列:
\(K_1\rightarrow K_2\rightarrow ... \rightarrow K_{i}\rightarrow K_{i+1}\rightarrow ... \rightarrow K_{n}\)
\(K_{i}\)之後的連結串列都已經完成了反轉,如下:
\(K_1\rightarrow K_2\rightarrow ... \rightarrow K_{i}\leftarrow K_{i+1}\leftarrow ... \leftarrow K_{n}\)

下一步該如何操作呢?
Ki->next->next = ki

因此可以據此寫出遞迴程式碼:

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if (head == nullptr || head->next == nullptr)
            return head;
        
        ListNode* p = reverseList(head->next);
        head->next->next = head;
        head->next = nullptr;
        return p;
    }
};

時間複雜度:\(O(n)\)
空間複雜度:\(O(n)\)

迭代反轉連結串列

定義一個prev指標作為新連結串列頭指標,使用頭插法將待反轉連結串列中的節點按順序插入到prev連結串列中,返回。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *prev = nullptr;
        ListNode *curr = head;
        while (curr != nullptr) {
            ListNode* tmp = curr->next; 
            curr->next = prev;
            prev = curr;
            curr = tmp;
        }

        return prev;
    }
};

時間複雜度:\(O(n)\)
空間複雜度:\(O(1)\)

2. 連結串列中部分節點的反轉

對於連結串列中部分節點的反轉,分為三步:

  • 定位到待反轉的節點
  • 斷鏈+反轉
  • 連線斷鏈

程式碼實現:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseBetween(ListNode* head, int m, int n) {
        if (head == nullptr || head->next == nullptr || m == n) 
            return nullptr;

        // 定位,使curr指向第m個節點
        ListNode *prev = nullptr, *curr = head;
        while (m > 1) {     
            prev = curr;
            curr = curr->next;
            --m;
            --n;
        }

        // 斷鏈反轉,得到反轉後的連結串列third
        ListNode *tail = curr, *third = nullptr, *tmp;
        while (n > 0) {     
            tmp = curr->next;
            curr->next = third;
            third = curr;
            curr = tmp;
            --n;
        }

        // 重新連線上斷鏈
        tail->next = tmp;
        if (prev != nullptr) {  
            prev->next = third;
        } else {          
            head = third;    // 若m=1,將斷鏈的頭指標賦給head
        } 
            
        return head;
    }
};

時間複雜度:\(O(n)\)
空間複雜度:\(O(1)\)

總結

對於連結串列反轉的題目,最簡單的辦法就是新建一個數組來儲存連結串列,然後逆序地重新構建新連結串列,但是這種做法的時間和空間開銷都比較大;更好的辦法是在原連結串列上直接反轉,即前述的兩種方法:迭代頭插法與遞迴反轉,迭代頭插法效率高,而遞迴反轉的程式碼實現非常簡潔。
在做連結串列題的時候在紙上畫一下圖能夠使操作過程變得更清晰。