1. 程式人生 > 其它 >206.反轉連結串列(簡單)

206.反轉連結串列(簡單)

206.反轉連結串列

題目連結:206. 反轉連結串列(簡單)

題目描述

給你單鏈表的頭節點 head ,請你反轉連結串列,並返回反轉後的連結串列。

示例 1:

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

示例 2:

輸入:head = [1,2]
輸出:[2,1]

示例 3:

輸入:head = []
輸出:[]

提示:

  • 連結串列中節點的數目範圍是 [0, 5000]

  • -5000 <= Node.val <= 5000

進階:連結串列可以選用迭代或遞迴方式完成反轉。你能否用兩種方法解決這道題?

題解

思路:如下圖,利用三個指標

程式碼(C++版本)

ListNode* reverseList(ListNode* head) {
    
//連結串列中只有沒有節點和只有一個節點的情況 if (head == NULL || head->next ==NULL) { return head; } ListNode* fp = head; ListNode* sp = fp->next; while (sp->next != NULL) { ListNode* tp = sp->next; sp->next = fp; fp = sp; sp = tp; } sp->next = fp; head
->next = NULL; head = sp; return head; }

分析:

  • 時間複雜度:O(N)

  • 空間複雜度:O(1)

官方題解

連結:官方題解

方法一:迭代 假設連結串列為 1→2→3→∅,我們想要把它改成 ∅←1←2←3。

在遍歷連結串列時,將當前節點的next 指標改為指向前一個節點。由於節點沒有引用其前一個節點,因此必須事先儲存其前一個節點。在更改引用之前,還需要儲存後一個節點。最後返回新的頭引用。

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

複雜度分析

  • 時間複雜度:O(n),其中 n 是連結串列的長度。需要遍歷連結串列一次。

  • 空間複雜度:O(1)。

方法二:遞迴 遞迴版本稍微複雜一些,其關鍵在於反向工作。假設連結串列的其餘部分已經被反轉,現在應該如何反轉它前面的部分?

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

複雜度分析

  • 時間複雜度:O(n),其中 n 是連結串列的長度。需要對連結串列的每個節點進行反轉操作。

  • 空間複雜度:O(n),其中 n 是連結串列的長度。空間複雜度主要取決於遞迴呼叫的棧空間,最多為 n 層。

程式碼隨想錄

程式碼隨想錄

思路

如果再定義一個新的連結串列,實現連結串列元素的反轉,其實這是對記憶體空間的浪費。

其實只需要改變連結串列的next指標的指向,直接將連結串列反轉 ,而不用重新定義一個新的連結串列,如圖所示:

之前連結串列的頭節點是元素1, 反轉之後頭結點就是元素5 ,這裡並沒有新增或者刪除節點,僅僅是改表next指標的方向。

雙指標法

那麼接下來看一看是如何反轉呢?

我們拿有示例中的連結串列來舉例,如動畫所示:

首先定義一個cur指標,指向頭結點,再定義一個pre指標,初始化為null。

然後就要開始反轉了,首先要把 cur->next 節點用tmp指標儲存一下,也就是儲存一下這個節點。

為什麼要儲存一下這個節點呢,因為接下來要改變 cur->next 的指向了,將cur->next 指向pre ,此時已經反轉了第一個節點了。

接下來,就是迴圈走如下程式碼邏輯了,繼續移動pre和cur指標。

最後,cur 指標已經指向了null,迴圈結束,連結串列也反轉完畢了。 此時我們return pre指標就可以了,pre指標就指向了新的頭結點。

程式碼(C++)

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode* temp; // 儲存cur的下一個節點
        ListNode* cur = head;
        ListNode* pre = NULL;
        while(cur) {
            temp = cur->next;  // 儲存一下 cur的下一個節點,因為接下來要改變cur->next
            cur->next = pre; // 翻轉操作
            // 更新pre 和 cur指標
            pre = cur;
            cur = temp;
        }
        return pre;
    }
};

遞迴法

遞迴法相對抽象一些,但是其實和雙指標法是一樣的邏輯,同樣是當cur為空的時候迴圈結束,不斷將cur指向pre的過程。

關鍵是初始化的地方,可能有的同學會不理解, 可以看到雙指標法中初始化 cur = head,pre = NULL,在遞迴法中可以從如下程式碼看出初始化的邏輯也是一樣的,只不過寫法變了。

具體可以看程式碼(已經詳細註釋),雙指標法寫出來之後,理解如下遞迴寫法就不難了,程式碼邏輯都是一樣的。

class Solution {
public:
    ListNode* reverse(ListNode* pre,ListNode* cur){
        if(cur == NULL) return pre;
        ListNode* temp = cur->next;
        cur->next = pre;
        // 可以和雙指標法的程式碼進行對比,如下遞迴的寫法,其實就是做了這兩步
        // pre = cur;
        // cur = temp;
        return reverse(cur,temp);
    }
    ListNode* reverseList(ListNode* head) {
        // 和雙指標法初始化是一樣的邏輯
        // ListNode* cur = head;
        // ListNode* pre = NULL;
        return reverse(NULL, head);
    }
​
};