LeetCode 99. 恢復(fù)二叉搜索樹 | Python

99. 恢復(fù)二叉搜索樹


題目來源:力扣(LeetCode)https://leetcode-cn.com/problems/recover-binary-search-tree

題目


二叉搜索樹中的兩個(gè)節(jié)點(diǎn)被錯(cuò)誤地交換。

請?jiān)诓桓淖兤浣Y(jié)構(gòu)的情況下,恢復(fù)這棵樹。

示例 1:

輸入: [1,3,null,null,2]

   1
  /
 3
  \
   2

輸出: [3,1,null,null,2]

   3
  /
 1
  \
   2

示例 2:

輸入: [3,1,4,null,null,2]

  3
 / \
1   4
   /
  2

輸出: [2,1,4,null,null,3]

  2
 / \
1   4
   /
  3

進(jìn)階:

  • 使用 O(n) 空間復(fù)雜度的解法很容易實(shí)現(xiàn)。
  • 你能想出一個(gè)只使用常數(shù)空間的解決方案嗎?

解題思路


思路:中序遍歷(遞歸),Morris算法

題目中說明,二叉搜索樹中的兩個(gè)節(jié)點(diǎn)被錯(cuò)誤地交換,需要在不改變結(jié)構(gòu)的情況下恢復(fù)二叉搜索樹。

我們知道,使用中序遍歷二叉搜索樹時(shí),得到的序列必然是遞增的。如果二叉搜索樹的節(jié)點(diǎn)被錯(cuò)誤交換,那么使用中序遍歷,就能夠定位到錯(cuò)誤的節(jié)點(diǎn)。

假設(shè)有一個(gè)遞增的序列 [1, 2, 3, 4],如果我們交換相鄰的位置,比如 2 和 3,那么序列就會(huì)變?yōu)?[1, 3, 2, 4]。此時(shí),這里會(huì)出現(xiàn)一處錯(cuò)誤點(diǎn),因?yàn)?3 > 2 不滿足序列遞增。如果我們交換不相鄰的位置,例如 1 和 4,那么序列就會(huì)變?yōu)?[4, 2, 3, 1],此時(shí),會(huì)出現(xiàn)兩個(gè)錯(cuò)誤點(diǎn),4 > 23 > 1 不滿足序列遞增。

這里,我們可以判斷,當(dāng)兩個(gè)節(jié)點(diǎn)被錯(cuò)誤交換時(shí),最多會(huì)產(chǎn)生兩處錯(cuò)誤。

這里額外提及下,因?yàn)榍懊娼o的例子,是先給定的遞增序列,我們假設(shè)取交換兩個(gè)節(jié)點(diǎn)。但如果給的是已經(jīng)交換的節(jié)點(diǎn),如何將節(jié)點(diǎn)恢復(fù)至正確的位置。從上面假設(shè)的分析中,我們可以看到,如果是相鄰的節(jié)點(diǎn)被交換,那么我們只要再次交換兩個(gè)節(jié)點(diǎn)即可;但如果不是相鄰節(jié)點(diǎn)被交換,我們可以發(fā)現(xiàn),兩處錯(cuò)誤點(diǎn),只要將前面第一處錯(cuò)誤的左邊節(jié)點(diǎn),與后面第二處錯(cuò)誤的右邊節(jié)點(diǎn)交換,即可恢復(fù)。

在這里,我們能直接想到的就是,利用一個(gè)數(shù)組,去存儲(chǔ)使用中序遍歷二叉搜索樹的值序列,只要能夠找到錯(cuò)誤的地方,就能夠?qū)⑵涓?/p>

中序遍歷(遞歸)

但這里,我們不使用額外的數(shù)組去存儲(chǔ)使用中序遍歷二叉搜索樹的值序列。因?yàn)榍懊娓鶕?jù)分析能夠判斷,當(dāng)兩個(gè)節(jié)點(diǎn)被錯(cuò)誤交換,最多會(huì)出現(xiàn)兩個(gè)錯(cuò)誤。那么我們只要關(guān)心中序遍歷的值序列每個(gè)相鄰的位置大小是否不對。下面是具體的算法(使用中序遍歷訪問):

  • 在遍歷訪問的過程中,用變量 pre_node 記錄當(dāng)前遍歷的最后一個(gè)節(jié)點(diǎn)。
  • 當(dāng)進(jìn)行下個(gè)節(jié)點(diǎn)的遍歷時(shí),用當(dāng)前節(jié)點(diǎn)的值與 pre_node 節(jié)點(diǎn)的值進(jìn)行比較。如果 pre_node.val >= cur_node.val,也就說明當(dāng)前位置出錯(cuò)。
  • 繼續(xù)向下遍歷,若能找到第二處位置時(shí),可以直接結(jié)束遍歷。
  • 當(dāng)確定錯(cuò)誤的位置時(shí),將節(jié)點(diǎn)值進(jìn)行交換。

在這里,我們用遞歸來實(shí)現(xiàn)這個(gè)算法。具體代碼見【代碼實(shí)現(xiàn) # 中序遍歷(遞歸)】

注意: 這個(gè)算法,在最差的情況下,也就是需要交換的位置出現(xiàn)在樹的最右側(cè)的葉子節(jié)點(diǎn)中,那么這個(gè)算法同樣還是會(huì)遍歷整個(gè)二叉搜索樹。

Morris 算法

在題目進(jìn)階處提及,是否能夠?qū)崿F(xiàn)常數(shù)時(shí)間復(fù)雜度的算法。在官方題解中提及的就是 Morris 算法,并且也進(jìn)一步描述了這個(gè)算法的信息。如果有興趣了解的話,可以從下方的鏈接入口繼續(xù)了解。

https://leetcode-cn.com/problems/recover-binary-search-tree/solution/hui-fu-er-cha-sou-suo-shu-by-leetcode-solution/

那么在這里,筆者就僅使用 Morris 的思想去嘗試解決該問題,就不再額外展開描述。

具體的代碼見【代碼實(shí)現(xiàn) # Morris 算法】

代碼實(shí)現(xiàn)


# 中序遍歷(遞歸)
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        # 用 pre_node 記錄遍歷的最后一個(gè)節(jié)點(diǎn)
        self.pre_node = TreeNode(float('-inf'))
        # 用來標(biāo)記兩個(gè)錯(cuò)誤
        self.first_error_node = None
        self.second_error_node = None
        self.count = 0

        def in_order(root):
            if not root:
                return None
            in_order(root.left)
            # 比較 pre_node 值與當(dāng)前節(jié)點(diǎn)的值
            if self.pre_node.val >= root.val and self.first_error_node == None:
                # 如果 pre_node >= root.val 表示此處出錯(cuò),進(jìn)行記錄
                self.first_error_node = self.pre_node
            if self.pre_node.val >= root.val and self.first_error_node:
                # 這里標(biāo)記第二處錯(cuò)誤,無論是出現(xiàn)一次錯(cuò)誤還是兩次錯(cuò)誤都適用
                self.second_error_node =  root
                # 出現(xiàn)兩次可以終止
                self.count += 1
                if self.count == 2:
                    return
            # 維護(hù) pre_node
            self.pre_node = root
            in_order(root.right)
        
        in_order(root)
        # 交換節(jié)點(diǎn)
        self.first_error_node.val, self.second_error_node.val = self.second_error_node.val, self.first_error_node.val

# Morris 算法
# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    def recoverTree(self, root: TreeNode) -> None:
        """
        Do not return anything, modify root in-place instead.
        """
        first_error = None
        second_error = None
        pre_node = TreeNode(float('-inf'))
        predecessor = None

        while root:
            if root.left:
                predecessor = root.left
                # 當(dāng)節(jié)點(diǎn)有左孩子時(shí),找到當(dāng)前節(jié)點(diǎn)的最右的節(jié)點(diǎn)
                while predecessor.right and predecessor.right != root:
                    predecessor = predecessor.right
                
                # 如果 predecessor 節(jié)點(diǎn)的右孩子為 None,將右指針指向 root,然后遍歷左子樹
                if predecessor.right == None:
                    predecessor.right = root
                    root = root.left
                # 若 predecessor 節(jié)點(diǎn)的右孩子不為空,同樣將指針指向 root
                # 同時(shí)說明左子樹遍歷完成,置空 predecessor 右孩子,訪問 root 的右孩子
                else:
                    # 沒有左孩子,直接訪問右孩子
                    if pre_node.val >= root.val and first_error == None:
                        first_error = pre_node
                    if pre_node.val >= root.val and first_error:
                        second_error = root
                    pre_node = root
                    # 置空 predecessor
                    predecessor.right = None
                    root = root.right
            else:
                # 沒有左孩子,直接訪問右孩子
                if pre_node.val >= root.val and first_error == None:
                    first_error = pre_node
                if pre_node.val >= root.val and first_error:
                    second_error = root
            
                pre_node = root

                root = root.right
        
        first_error.val, second_error.val = second_error.val, first_error.val

實(shí)現(xiàn)結(jié)果


實(shí)現(xiàn)結(jié)果 | 中序遍歷(遞歸)
實(shí)現(xiàn)結(jié)果 | Morris 算法

歡迎關(guān)注


公眾號 【書所集錄

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容