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