給你一個二叉樹的根節點 root ,判斷其是否是一個有效的二叉搜索樹。
有效 二叉搜索樹定義如下:
節點的左子樹只包含 小於 當前節點的數。
節點的右子樹只包含 大於 當前節點的數。
所有左子樹和右子樹自身必須也是二叉搜索樹。
示例 1:
輸入:root = [2,1,3] 輸出:true
示例 2:
輸入:root = [5,1,4,null,null,3,6]
輸出:false
解釋:根節點的值是 5,但是右子節點的值是 4。
【分析】
方法一:遞歸
要解決這道題首先我們要了解二叉搜索樹有什麼性質可以給我們利用,由題目給出的信息我們可以知道:如果二叉樹的左子樹不爲空,則左子樹所有節點的值均小於它的根節點的值;若它的右子樹不爲空,則右子樹上所有節點的值均大於它的根節點的值;它的左右子樹也是二叉搜索樹。
這啓示我們設計一個遞歸函數helper(root, lower, upper)來遞歸判斷,函數表示考慮以root爲根的子樹,判斷子樹中所有節點的值是否都在(l, r)的範圍內(注意是開區間)。若root節點的值val不在(l, r)範圍內說明不滿足條件直接返回,否則我們要繼續遞歸調用檢查它的左右子樹是否滿足,若都滿足才說明這是一棵二叉搜索樹。
那麼根據二叉搜索樹的性質,在遞歸調用左子樹時,我們需要把上界upper改爲root.val,即調用helper(root.left, lower, root.val),因爲左子樹裏所有節點的值均小於它的根節點的值。同理遞歸調用右子樹時,需要把下界lower改爲root.val,即調用helper(root.right, root.val, upper)。
函數遞歸調用的入口爲helper(root, -inf, +inf),inf表示一個無窮大的值。
下圖展示了算法如何在示例2上運行:
# 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 isValidBST(self, root: Optional[TreeNode]) -> bool: def BFS(root, lower, upper): if not root: return True if lower < root.val < upper: return BFS(root.left, lower, root.val) and BFS(root.right, root.val, upper) else: return False return BFS(root, -float('inf'), float('inf'))
時間複雜度:O(n),其中 n 爲二叉樹的節點個數。在遞歸調用的時候二叉樹的每個節點最多被訪問一次,因此時間複雜度爲 O(n)。
空間複雜度:O(n),其中 n 爲二叉樹的節點個數。遞歸函數在遞歸過程中需要爲每一層遞歸函數分配棧空間,所以這裏需要額外的空間且該空間取決於遞歸的深度,即二叉樹的高度。最壞情況下二叉樹爲一條鏈,樹的高度爲 n ,遞歸最深達到 n 層,故最壞情況下空間複雜度爲 O(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 isValidBST(self, root: Optional[TreeNode]) -> bool: stack, inorder = [], float('-inf') while stack or root: # 當棧或者根節點非空 while root: # 當根節點非空 stack.append(root) # 將根節點入棧 root = root.left # 找到根節點的左子樹,將其設置爲新的root root = stack.pop() # 直到左子樹爲空,將最近一個root也就是左節點出棧 # 若中序遍歷得到的節點的值小於等於前一個inorder, 說明不是二叉搜索樹 if root.val <= inorder: return False inorder = root.val root = root.right # 繼續遍歷右子樹 return True # 時間複雜度:O(n),其中 n 爲二叉樹的節點個數。二叉樹的每個節點最多被訪問一次,因此時間複雜度爲 O(n)。 # 空間複雜度:O(n),其中 n 爲二叉樹的節點個數。棧最多存儲 n 個節點,因此需要額外的 O(n) 的空間。
提示:
樹中節點數目範圍在[1, 104] 內
-231 <= Node.val <= 231 - 1