輸入一個樹,判斷該樹是不是合法二分查找樹,95題作過生成二分查找樹。二分查找樹定義以下:java
- 若任意節點的左子樹不空,則左子樹上全部節點的值均小於它的根節點的值;
- 若任意節點的右子樹不空,則右子樹上全部節點的值均大於它的根節點的值;
- 任意節點的左、右子樹也分別爲二叉查找樹;
- 沒有鍵值相等的節點。
開始的時候覺得能夠很簡單的用遞歸寫出來。想法是,左子樹是合法二分查找樹,右子樹是合法二分查找樹,而且根節點大於左孩子,小於右孩子,那麼當前樹就是合法二分查找樹。代碼以下:node
public boolean isValidBST(TreeNode root) { if (root == null) { return true; } boolean leftVailid = true; boolean rightVaild = true; if (root.left != null) { //大於左孩子而且左子樹是合法二分查找樹 leftVailid = root.val > root.left.val && isValidBST(root.left); } if (!leftVailid) { return false; } if (root.right != null) { //小於右孩子而且右子樹是合法二分查找樹 rightVaild = root.val < root.right.val && isValidBST(root.right); } return rightVaild; }
固然,這個解法沒有經過。對於下面的解,結果利用上邊的解法是錯誤的。數組
10 / \ 5 15 / \ 6 20
雖然知足左子樹是合法二分查找樹,右子樹是合法二分查找樹,而且根節點大於左孩子,小於右孩子,但這個樹不是合法的二分查找樹。由於右子樹中的 6 小於當前根節點 10。因此咱們不該該判斷「根節點大於左孩子,小於右孩子」,而是判斷「根節點大於左子樹中最大的數,小於右子樹中最小的數」。spa
public boolean isValidBST(TreeNode root) { if (root == null || root.left == null && root.right == null) { return true; } //左子樹是否合法 if (isValidBST(root.left)) { if (root.left != null) { int max = getMaxOfBST(root.left);//獲得左子樹中最大的數 if (root.val <= max) { //相等的狀況,表明有重複的數字 return false; } } } else { return false; } //右子樹是否合法 if (isValidBST(root.right)) { if (root.right != null) { int min = getMinOfBST(root.right);//獲得右子樹中最小的數 if (root.val >= min) { //相等的狀況,表明有重複的數字 return false; } } } else { return false; } return true; } private int getMinOfBST(TreeNode root) { int min = root.val; while (root != null) { if (root.val <= min) { min = root.val; } root = root.left; } return min; } private int getMaxOfBST(TreeNode root) { int max = root.val; while (root != null) { if (root.val >= max) { max = root.val; } root = root.right; } return max; }
來利用另外一種思路,參考官方題解。3d
解法一中,咱們是判斷根節點是否合法,找到了左子樹中最大的數,右子樹中最小的數。 由左子樹和右子樹決定當前根節點是否合法。code
但若是正常的來說,明明先有的根節點,按理說根節點是任何數都行,而不是由左子樹和右子樹限定。相反,根節點反而決定了左孩子和右孩子的合法取值範圍。對象
因此,咱們能夠從根節點進行 DFS,而後計算每一個節點應該的取值範圍,若是當前節點不符合就返回 false。blog
10 / \ 5 15 / \ / 3 6 7 考慮 10 的範圍 10(-inf,+inf) 考慮 5 的範圍 10(-inf,+inf) / 5(-inf,10) 考慮 3 的範圍 10(-inf,+inf) / 5(-inf,10) / 3(-inf,5) 考慮 6 的範圍 10(-inf,+inf) / 5(-inf,10) / \ 3(-inf,5) 6(5,10) 考慮 15 的範圍 10(-inf,+inf) / \ 5(-inf,10) 15(10,+inf) / \ 3(-inf,5) 6(5,10) 考慮 7 的範圍,出現不符合返回 false 10(-inf,+inf) / \ 5(-inf,10) 15(10,+inf) / \ / 3(-inf,5) 6(5,10) 7(10,15)
能夠觀察到,左孩子的範圍是 (父結點左邊界,父節點的值),右孩子的範圍是(父節點的值,父節點的右邊界)。遞歸
還有個問題,java 裏邊沒有提供負無窮和正無窮,用什麼數來表示呢?隊列
方案一,假設咱們的題目的數值都是 Integer 範圍的,那麼咱們用不在 Integer 範圍的數字來表示負無窮和正無窮。用 long 去存儲。
public boolean isValidBST(TreeNode root) { long maxValue = (long)Integer.MAX_VALUE + 1; long minValue = (long)Integer.MIN_VALUE - 1; return getAns(root, minValue, maxValue); } private boolean getAns(TreeNode node, long minVal, long maxVal) { if (node == null) { return true; } if (node.val <= minVal) { return false; } if (node.val >= maxVal) { return false; } return getAns(node.left, minVal, node.val) && getAns(node.right, node.val, maxVal); }
方案二:傳入 Integer 對象,而後 null 表示負無窮和正無窮。而後利用 JAVA 的自動裝箱拆箱,數值的比較能夠直接用不等號。
public boolean isValidBST(TreeNode root) { return getAns(root, null, null); } private boolean getAns(TreeNode node, Integer minValue, Integer maxValue) { if (node == null) { return true; } if (minValue != null && node.val <= minValue) { return false; } if (maxValue != null && node.val >= maxValue) { return false; } return getAns(node.left, minValue, node.val) && getAns(node.right, node.val, maxValue); }
解法二其實就是樹的 DFS,也就是二叉樹的先序遍歷,而後在遍歷過程當中,判斷當前的值是是否在區間中。因此咱們能夠用棧來模擬遞歸過程。
public boolean isValidBST(TreeNode root) { if (root == null || root.left == null && root.right == null) { return true; } //利用三個棧來保存對應的節點和區間 LinkedList<TreeNode> stack = new LinkedList<>(); LinkedList<Integer> minValues = new LinkedList<>(); LinkedList<Integer> maxValues = new LinkedList<>(); //頭結點入棧 TreeNode pNode = root; stack.push(pNode); minValues.push(null); maxValues.push(null); while (pNode != null || !stack.isEmpty()) { if (pNode != null) { //判斷棧頂元素是否符合 Integer minValue = minValues.peek(); Integer maxValue = maxValues.peek(); TreeNode node = stack.peek(); if (minValue != null && node.val <= minValue) { return false; } if (maxValue != null && node.val >= maxValue) { return false; } //將左孩子加入到棧 if(pNode.left!=null){ stack.push(pNode.left); minValues.push(minValue); maxValues.push(pNode.val); } pNode = pNode.left; } else { // pNode == null && !stack.isEmpty() //出棧,將右孩子加入棧中 TreeNode node = stack.pop(); minValues.pop(); Integer maxValue = maxValues.pop(); if(node.right!=null){ stack.push(node.right); minValues.push(node.val); maxValues.push(maxValue); } pNode = node.right; } } return true; }
上邊的 DFS 能夠看出來一個缺點,就是咱們判斷完當前元素後並無出棧,後續還會回來獲得右孩子後纔會出棧。因此其實咱們能夠用 BFS,利用一個隊列,一層一層的遍歷,遍歷完一個就刪除一個。
public boolean isValidBST(TreeNode root) { if (root == null || root.left == null && root.right == null) { return true; } //利用三個隊列來保存對應的節點和區間 Queue<TreeNode> queue = new LinkedList<>(); Queue<Integer> minValues = new LinkedList<>(); Queue<Integer> maxValues = new LinkedList<>(); //頭結點入隊列 TreeNode pNode = root; queue.offer(pNode); minValues.offer(null); maxValues.offer(null); while (!queue.isEmpty()) { //判斷隊列的頭元素是否符合條件而且出隊列 Integer minValue = minValues.poll(); Integer maxValue = maxValues.poll(); pNode = queue.poll(); if (minValue != null && pNode.val <= minValue) { return false; } if (maxValue != null && pNode.val >= maxValue) { return false; } //左孩子入隊列 if(pNode.left!=null){ queue.offer(pNode.left); minValues.offer(minValue); maxValues.offer(pNode.val); } //右孩子入隊列 if(pNode.right!=null){ queue.offer(pNode.right); minValues.offer(pNode.val); maxValues.offer(maxValue); } } return true; }
參考這裏。
解法三中咱們用了先序遍歷 和 BFS,如今來考慮中序遍歷。中序遍歷在 94 題中已經考慮過了。那麼中序遍歷在這裏有什麼好處呢?
中序遍歷順序會是左孩子,根節點,右孩子。二分查找樹的性質,左孩子小於根節點,根節點小於右孩子。
是的,若是咱們將中序遍歷的結果輸出,那麼將會到的一個從小到大排列的序列。
因此咱們只須要進行一次中序遍歷,將遍歷結果保存,而後判斷該數組是不是從小到大排列的便可。
更近一步,因爲咱們只須要臨近的兩個數的相對關係,因此咱們只須要在遍歷過程當中,把當前遍歷的結果和上一個結果比較便可。
public boolean isValidBST(TreeNode root) { if (root == null) return true; Stack<TreeNode> stack = new Stack<>(); TreeNode pre = null; while (root != null || !stack.isEmpty()) { while (root != null) { stack.push(root); root = root.left; } root = stack.pop(); if(pre != null && root.val <= pre.val) return false; pre = root; root = root.right; } return true; }
這幾天都是二叉樹的相關題,主要是對前序遍歷,中序遍歷的理解,以及 DFS,若是再用好遞歸,利用棧模擬遞歸,題目就很好解了。
更多詳細通俗題解詳見 leetcode.wang 。