問題
最近研究算法,遇到的一道很有意思的問題——怎麼把一個鏈表反轉?
很容易想到一個方法:遍歷鏈表,數組作棧存儲路徑,元素逐個出棧得到的就是反轉後的鏈表!查找資料發現,有更好的方式實現。
仔細研究後,終於明白了其中的奧妙。小僧掌握了兩種方法,以下分別進行說明。
首先給出鏈表結構:
public class LinkedNode {
Integer id ;
LinkedNode next;
public LinkedNode(Integer id) {
this.id = id;
}
}
下一步構造出上圖的鏈表結構:
LinkedNode node1 = new LinkedNode(1);
LinkedNode node2 = new LinkedNode(2);
LinkedNode node3 = new LinkedNode(3);
LinkedNode node4 = new LinkedNode(4);
node1.next = node2;
node2.next = node3;
node3.next = node4;
雙指針遍歷法
先給出代碼實現:
/**
* 鏈表翻轉,循環 + 雙指針(pre、next)實現
* @param cur
* @return
*/
public LinkedNode reverse(LinkedNode cur){
LinkedNode pre = null;
while (cur!=null){
LinkedNode next = cur.next; // 1.
cur.next = pre; // 2.
pre = cur; // 3.
cur = next; // 4.
}
return pre;
}
循環體之前,鏈表示意圖:
之後進入while
循環,註釋標註的四個步驟會產生如下變化(圖中編號與註釋編號一一對應):
第一次循環後,鏈表變成這樣:
之後的遍歷,鏈表的變化示意:
可見,while
循環執行完,pre
指向的節點,已經是最新的頭節點了!
遞歸法
遞歸的實現方式,似乎更容易理解。
/**
* 鏈表反轉,遞歸實現
* @param node
* @return
*/
public LinkedNode reverse2(LinkedNode node){
if(node.next==null){
return node;
}
LinkedNode newHead = reverse2(node.next);
node.next.next = node; //node.next.next 換成 newHead.next 不行,因爲node在遞歸中在追溯上一個節點,仔細體會下
node.next = null;
return newHead;
}
首先會通過遞歸調用找到尾節點,之後做了兩件事:
- 尾節點反指 (
node.next.next = node;
) - 當前節點指向null節點 (
node.next = null;
)
rentun newHead
後,回溯到節點2(此時node就是節點2),再次重複之前的兩件事——節點反指和當前節點指向null節點。
再次回溯,得到最終的結果。
老規矩,完整代碼見git:暗夜君王的demo練習——鏈表反轉
Done !