劍指offer_36_二叉搜尋樹與雙向連結串列
二叉搜尋樹與雙向連結串列
題目連結:https://leetcode-cn.com/problems/er-cha-sou-suo-shu-yu-shuang-xiang-lian-biao-lcof/
題目描述:輸入一棵二叉搜尋樹,將該二叉搜尋樹轉換成一個排序的迴圈雙向連結串列。要求不能建立任何新的節點,只能調整樹中節點指標的指向。
為了讓您更好地理解問題,以下面的二叉搜尋樹為例:
我們希望將這個二叉搜尋樹轉化為雙向迴圈連結串列。連結串列中的每個節點都有一個前驅和後繼指標。對於雙向迴圈連結串列,第一個節點的前驅是最後一個節點,最後一個節點的後繼是第一個節點。
下圖展示了上面的二叉搜尋樹轉化成的連結串列。“head” 表示指向連結串列中有最小元素的節點。
特別地,我們希望可以就地完成轉換操作。當轉化完成以後,樹中節點的左指標需要指向前驅,樹中節點的右指標需要指向後繼。還需要返回連結串列中的第一個節點的指標。
題目解析
題目解析內容來自於題解中Krahets
題目的意思還是很明確的。基於二叉搜尋樹的性質,我們使用中序遍歷可得到二叉搜尋樹的遞增序列
將 二叉搜尋樹 轉換成一個 排序的迴圈雙線連結串列 ,其中包含三個要素:
- 排序連結串列:節點應該從小到大排序,因此應使用 中序遍歷 從小到大 訪問數節點;
- 雙向連結串列:在構建相鄰節點(設前驅節點 pre,當前節點 cur )關係時,不僅應 pre.right = cur, cur.left = pre。
- 迴圈連結串列:設連結串列頭結點 head 和尾結點 tail , 則應構建 head.left = tail 和 tail.right = head。
中序遍歷 為 “左 根 右” 順序,遞迴實現程式碼如下:
# 中序遍歷 def in_traversal(root): ret = [] def traversal(root): if not root: return traversal(root.left) ret.append(root.val) traversal(root.right) traversal(root) return ret
根據以上分析,考慮使用中序遍歷訪問樹的各節點 cur
;並在訪問每個節點時構建 cur
和前驅節點 pre
的引用指向;中序遍歷完成後,最後構建頭節點和尾節點的引用指向即可。
演算法流程
in_traversal(cur)
: 遞迴法中序遍歷
-
終止條件:當節點
cur
為空時,代表已經越過葉子節點,此時可直接返回 -
遞迴左子樹,即
in_traversal(cur.left)
-
構建連結串列:
- 當
pre
為空時:代表當前訪問的是連結串列的頭結點,記為head
- 當
pre
不為空時:修改雙向節點引用,即pre.right = cur
,cur.left = pre
; - 儲存
cur
:更新pre = cur
, 即節點cur
是後繼節點的pre
- 當
- 當
-
遞迴右子樹,即
in_traversal(cur.right)
treeToDoublyList(root)
:
- 特例處理:若節點
root
為空,則直接返回; - 初始化:空節點
pre
- 轉化為雙向連結串列:呼叫
in_traversal(root)
; - 構建迴圈連結串列:中序遍歷完成後,
head
指向頭節點,pre
指向尾結點,因此修改head
和pre
的雙向節點引用即可 - 返回值:返回連結串列的頭節點
head
即可
複雜度分析
- 時間複雜度:O(N)
- 空間複雜度:O(N)
程式碼
class Solution:
def treeToDoublyList(self, root: 'Node') -> 'Node':
def in_traversal(cur):
if not cur:
return
in_traversal(cur.left) # 遞迴左子樹
if self.pre: # 修改節點引用
self.pre.right, cur.left = cur, self.pre
else: # 記錄頭節點
self.head = cur
self.pre = cur # 儲存 cur
in_traversal(cur.right) # 遞迴右子樹
if not root:
return
self.pre = None
in_traversal(root)
self.head.left, self.pre.right = self.pre, self.head
return self.head