反轉連結串列這題真的是面試非常喜歡考的了,這題看起來簡單,但是能用兩種方法一遍 bug free 也是不容易的,面試的時候可以篩下來一大批人,無論是對 junior 還是 senior 面試都很愛考。
今天齊姐就帶你梳理清楚思路,思路清楚了才能寫碼如有神。
題目
這是從力扣中文站上截下來的,但是這個輸出不太形象。
對連結串列的反轉,並不是要把它實際翻個個,只是動一動 next 指標就好了。
什麼意思呢?
我們先看對陣列進行反轉。
陣列是一個物理上連續儲存的資料結構,反轉之後原來放 1 的位置就變成了放 5.
但是連結串列並不是,因為連結串列在物理上是不連續的,它的每個單元 ListNode
是通過 next
指標連線在一起的,而每個 ListNode 之間在記憶體裡並不一定是挨著的。
所以反轉連結串列,就不是非要把 1 的位置放 5,因為它們想在哪在哪。
那麼怎麼保證這個順序呢?
- 就是 next 指標。
沿著 next 指標的方向走下去,就是連結串列的順序。這也就保證了,只要我們拿到了頭節點,就掌控了整個 LinkedList.
那麼題目中的例子,形象點是這個樣子滴:
也就是元素自己不用動,只需要動動小指標,就是反轉了。
遞迴解法
遞迴的三步驟大家還記得嗎?
Base case + 拆解 + 組合
不記得的趕緊在公眾號內回覆「遞迴」二字,獲取遞迴的入門篇詳解。
那麼我們來看這個題的:
base case:
當只有一個 node,或者沒有 node 了唄,也就是
if(node == null || node.next == null) {
return node;
}
其實呢,只剩一個 node 的時候嚴格來講並不是 base case,而是 corner case,
因為它本可以再 break down 到 node == null 的,但因為後面有對 node.next 的 dereference 操作,所以不能省略。
拆解:
遞迴的拆解就是把大問題,分解成小一點點的問題,直到 base case 可以返回,進行第三步的組合。
那麼這裡就是
組合:
組合的意思是,假設我們能夠拿到小問題的解,那麼用小問題的解去構造大問題的解。
那麼這個問題裡如何構造呢?
這裡很明顯,在 2 後面接上 1 就行了,但是怎麼拿到 2 呢?
別忘了,原問題裡,此時還有 1 指向 2 呢~
也就是 node1.next = node2
,
然後把 2 指向 1:node2.next = node1
合起來就是:node1.next.next = node1
思路清楚就不繞,看著覺得繞的就是沒想清楚哼~
程式碼
遞迴的程式碼寫起來都很簡潔:
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
時間複雜度
我們在「遞迴」這篇文章裡說過,遞迴的時間複雜度分析方法就是把遞迴樹畫出來,每個節點的時間加起來就行了。
這個遞迴樹是一個很簡單的單項鍊表,每個節點上做的就是那三行程式碼,也就是「組合」做的事,即 O(1) 的時間,總共有 n 個節點,那麼總的時間就是 O(n).
空間複雜度
那看遞迴樹也很明顯了,每一層也就用了點小變數,是 O(1),所以總的空間共是 O(n).
Iterative 解法
(誰能告訴我這個中文的專業說法。。
Iterative 的思路就是:
過一遍這個 Linked List,邊過邊把這個 node 的 next 指標指向前面那個 node,直到過完全部。
這樣說太抽象,面試時也是,直接過例子。
那也就是把 1 的 next 指標翻過來指向 NULL;
把 2 的 next 指標翻過來指向 1;
把 3 的 next 指標翻過來指向 2;
...
所以我們還需要一個變數來記錄當前 node 的前一個 node,不妨設為 prev.
同時呢,一旦我們把指標翻轉過去,後面的那個 node 就丟了有木有!所以還需要有個額外的變數事先記錄下後面的 node,設為 nxt,這樣才不至於走丟~
Step1.
翻轉箭頭:把 1 的 next 指標指向 NULL;
這樣子,同時我們也明確了,prev 的初始化應該是 NULL.
然後把這仨變數都移動到下一個位置:
Step2.
翻轉箭頭:把 2 的 next 指標指向 1,
然後三人行:
Step3.
翻轉箭頭:把 3 的 next 指標指向 2,
再齊步走:
Step4.
再把 4 的反過來:
再往後走:
Step5.
再把 5 的 next 反過來:
但是因為我們的 while 迴圈包含了
「翻轉箭頭」+「三人行」
兩個步驟,所以還需要走完最後一個三人行,變成:
很多同學搞不清楚這個 while 迴圈的結束條件,其實很簡單,你就走個例子畫畫圖嘛!
那結束條件就是 curr = null
的時候,
最後返回的是 prev
.
好了,看程式碼吧:
class Solution {
public ListNode reverseList(ListNode head) {
ListNode prev = null;
ListNode curr = head;
while(curr != null) {
ListNode nxt = curr.next;
curr.next = prev; // 翻轉箭頭
prev = curr; //三人行
curr = nxt; //三人行
}
return prev;
}
}
時間複雜度
這裡的時間複雜度很明顯了,就是過了一遍這個連結串列,所以是 O(n).
空間複雜度
空間是 O(1).
如果你喜歡這篇文章,記得給我點贊留言哦~你們的支援和認可,就是我創作的最大動力,我們下篇文章見!
我是小齊,紐約程式媛,終生學習者,每天晚上 9 點,雲自習室裡不見不散!
更多幹貨文章見我的 Github: https://github.com/xiaoqi6666...