[LeetCode] 333. Largest BST Subtree 最大的二分搜索子樹


 

Given a binary tree, find the largest subtree which is a Binary Search Tree (BST), where largest means subtree with largest number of nodes in it.

Note:
A subtree must include all of its descendants.

Example:

Input: [10,5,15,1,8,null,7]

   10 
   / \ 
  5  15 
 / \   \ 
1   8   7

Output: 3
Explanation: The Largest BST Subtree in this case is the highlighted one.
             The return value is the subtree's size, which is 3.

Follow up:
Can you figure out ways to solve it with O(n) time complexity?

Hint:

  1. You can recursively use algorithm similar to 98. Validate Binary Search Tree at each node of the tree, which will result in O(nlogn) time complexity.

 

這道題讓我們求一棵二分樹的最大二分搜索子樹,所謂二分搜索樹就是滿足左<根<右的二分樹,需要返回這個二分搜索子樹的節點個數。題目中給的提示說可以用之前那道 Validate Binary Search Tree 的方法來做,時間復雜度為 O(n2),這種方法是把每個節點都當做根節點,來驗證其是否是二叉搜索數,並記錄節點的個數,若是二叉搜索樹,就更新最終結果,參見代碼如下:

 

解法一:

class Solution {
public:
    int largestBSTSubtree(TreeNode* root) {
        int res = 0;
        dfs(root, res);
        return res;
    }
    void dfs(TreeNode *root, int &res) {
        if (!root) return;
        int d = countBFS(root, INT_MIN, INT_MAX);
        if (d != -1) {
            res = max(res, d);
            return;
        }
        dfs(root->left, res);
        dfs(root->right, res);
    }
    int countBFS(TreeNode *root, int mn, int mx) {
        if (!root) return 0;
        if (root->val <= mn || root->val >= mx) return -1;
        int left = countBFS(root->left, mn, root->val);
        if (left == -1) return -1;
        int right = countBFS(root->right, root->val, mx);
        if (right == -1) return -1;
        return left + right + 1;
    }
};

 

下面我們來看一種更簡潔的寫法,對於每一個節點,都來驗證其是否是 BST,如果是的話,就統計節點的個數即可,參見代碼如下:

 

解法二:

class Solution {
public:
    int largestBSTSubtree(TreeNode* root) {
        if (!root) return 0;
        if (isValid(root, INT_MIN, INT_MAX)) return count(root);
        return max(largestBSTSubtree(root->left), largestBSTSubtree(root->right));
    }
    bool isValid(TreeNode* root, int mn, int mx) {
        if (!root) return true;
        if (root->val <= mn || root->val >= mx) return false;
        return isValid(root->left, mn, root->val) && isValid(root->right, root->val, mx);
    }
    int count(TreeNode* root) {
        if (!root) return 0;
        return count(root->left) + count(root->right) + 1;
    }
};

 

題目中的 Follow up 讓用 O(n) 的時間復雜度來解決問題,還是采用 DFS 的思想來解題,由於時間復雜度的限制,只允許遍歷一次整個二叉樹,由於滿足題目要求的二叉搜索子樹必定是有葉節點的,所以思路就是先遞歸到最左子節點,然后逐層往上遞歸,對於每一個節點,都記錄當前最大的 BST 的節點數,當做為左子樹的最大值,和做為右子樹的最小值,當每次遇到左子節點不存在或者當前節點值大於左子樹的最大值,且右子樹不存在或者當前節點值小於右子樹的最小數時,說明 BST 的節點數又增加了一個,更新結果及其參數,如果當前節點不是 BST 的節點,那么更新 BST 的節點數 res 為左右子節點的各自的 BST 的節點數的較大值,參見代碼如下:

 

解法三:

class Solution {
public:
    int largestBSTSubtree(TreeNode* root) {
        int res = 0, mn = INT_MIN, mx = INT_MAX;
        isValidBST(root, mn, mx, res);
        return res;
    }
    void isValidBST(TreeNode* root, int& mn, int& mx, int& res) {
        if (!root) return;
        int left_cnt = 0, right_cnt = 0, left_mn = INT_MIN;
        int right_mn = INT_MIN, left_mx = INT_MAX, right_mx = INT_MAX;
        isValidBST(root->left, left_mn, left_mx, left_cnt);
        isValidBST(root->right, right_mn, right_mx, right_cnt);
        if ((!root->left || root->val > left_mx) && (!root->right || root->val < right_mn)) {
            res = left_cnt + right_cnt + 1;
            mn = root->left ? left_mn : root->val;
            mx = root->right ? right_mx : root->val;
        } else {
            res = max(left_cnt, right_cnt);    
        }
    }
};

 

上面的解法在遞歸函數中定義了大量的變量,難免讓人看的眼花繚亂,我們可以稍稍精簡一下,將這些變量都放到遞歸函數的返回值中,此時的helper函數返回了一個一維數組,里面有三個數字,分別是以當前結點為根結點的數的最小值,最大值,以及最大的 BST 子樹的結點個數。那么就可以在邊驗證 BST 的過程中邊統計個數,首先判空,若空,則返回一個默認三元組,整型最大值,最小值,和0。那你可能有疑問,定義的不是說第一個值是最小值么?沒錯,后面再解釋。若當前結點 node 存在,分別對其左右子結點調用遞歸函數,那么左子樹和右子樹的信息都保存到了 left 和 right 數組中,就算左右子結點不存在也沒關系,由於第一句的判空,還是會得到一個默認的三元組。接下來就是根據左右子樹的信息來更新結果 res 了,由於 BST 的定義,當前結點值肯定是大於左子樹的最大值,小於右子樹的最小值的。左子樹的最大值保存在 left[1] 中,右子樹的最小值保存在 right[0] 中,如果這兩個條件滿足了,說明左右子樹都是 BST,那么返回的三元組的最小值就是當前結點值和左子樹最小值中的較小者,最大值就是當前結點值和右子樹最大值中的較大值,返回的 BST 結點個數就是左右子樹的結點個數加上1,即算上了當前結點。好,現在解釋下為空時返回的三元組為何順序是整型最大值,整型最小值。如果當前是葉結點,其也算是 BST,那么肯定希望能進入 if 從句,從而使得三元組的第三項能加1,但是 if 的條件是當前結點值要大於左子樹中的最大值,現在左子結點是空的,為了保證條件能通過,我們將空的左子樹的最大值設置為整型最小值,這樣一定能通過,同理,將空的右子樹的最小值設置為整型最大值,這就是空結點的三元組的作用。好,繼續看 else 中的內容,如果破壞了 BST 的規則,則返回的三元組的最小值就是整型最小值,最大值是整型最大值,BST 結點個數並不是0,因為其左右子樹中有可能還有 BST,所以是左右子樹中的 BST 結點個數中的較大值,參見代碼如下:

 

解法四:

class Solution {
public:
    int largestBSTSubtree(TreeNode* root) {
        vector<int> res = helper(root);
        return res[2];
    }
    vector<int> helper(TreeNode* node) {
        if (!node) return {INT_MAX, INT_MIN, 0};
        vector<int> left = helper(node->left), right = helper(node->right);
        if (node->val > left[1] && node->val < right[0]) {
            return {min(node->val, left[0]), max(node->val, right[1]), left[2] + right[2] + 1};
        } else {
            return {INT_MIN, INT_MAX, max(left[2], right[2])};
        }
    }
};

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/333

 

類似題目:

Validate Binary Search Tree

 

參考資料:

https://leetcode.com/problems/largest-bst-subtree/

https://leetcode.com/problems/largest-bst-subtree/discuss/78892/12ms-C%2B%2B-solution

https://leetcode.com/problems/largest-bst-subtree/discuss/78899/Very-Short-Simple-Java-O(N)-Solution

https://leetcode.com/problems/largest-bst-subtree/discuss/78896/Clean-and-easy-to-understand-Java-Solution

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM