LeetCode 力扣 98. 驗證二叉搜索樹

題目描述(中等難度)

輸入一個樹,判斷該樹是不是合法二分查找樹,95題作過生成二分查找樹。二分查找樹定義以下:java

  1. 若任意節點的左子樹不空,則左子樹上全部節點的值均小於它的根節點的值;
  2. 若任意節點的右子樹不空,則右子樹上全部節點的值均大於它的根節點的值;
  3. 任意節點的左、右子樹也分別爲二叉查找樹;
  4. 沒有鍵值相等的節點。

解法一

開始的時候覺得能夠很簡單的用遞歸寫出來。想法是,左子樹是合法二分查找樹,右子樹是合法二分查找樹,而且根節點大於左孩子,小於右孩子,那麼當前樹就是合法二分查找樹。代碼以下: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 BFS

解法二其實就是樹的 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
相關文章
相關標籤/搜索