1. 程式人生 > >[LeetCode] Count Univalue Subtrees 計數相同值子樹的個數

[LeetCode] Count Univalue Subtrees 計數相同值子樹的個數

Given a binary tree, count the number of uni-value subtrees.

A Uni-value subtree means all nodes of the subtree have the same value.

For example:
Given binary tree,

              5
             / \
            1   5
           / \   \
          5   5   5

return 4.

這道題讓我們求相同值子樹的個數,就是所有節點值都相同的子樹的個數,之前有道求最大BST子樹的題

Largest BST Subtree,感覺挺像的,都是關於子樹的問題,解題思路也可以參考一下,我們可以用遞迴來做,第一種解法的思路是先序遍歷樹的所有的節點,然後對每一個節點呼叫判斷以當前節點為根的字數的所有節點是否相同,判斷方法可以參考之前那題Same Tree,用的是分治法的思想,分別對左右字數分別呼叫遞迴,參見程式碼如下:

解法一:

class Solution {
public:
    int res = 0;
    int countUnivalSubtrees(TreeNode* root) {
        if (!root) return res;
        
if (isUnival(root, root->val)) ++res; countUnivalSubtrees(root->left); countUnivalSubtrees(root->right); return res; } bool isUnival(TreeNode *root, int val) { if (!root) return true; return root->val == val && isUnival(root->left, val) && isUnival(root->right, val); } };

但是上面的那種解法不是很高效,含有大量的重複check,我們想想能不能一次遍歷就都搞定,我們這樣想,符合條件的相同值的字數肯定是有葉節點的,而且葉節點也都相同(注意單獨的一個葉節點也被看做是一個相同值子樹),那麼我們可以從下往上check,採用後序遍歷的順序,左右根,我們還是遞迴呼叫函式,對於當前遍歷到的節點,如果對其左右子節點分別遞迴呼叫函式,返回均為true的話,那麼說明當前節點的值和左右子樹的值都相同,那麼又多了一棵樹,所以結果自增1,然後返回當前節點值和給定值(其父節點值)是否相同,從而回歸上一層遞迴呼叫。這裡特別說明一下在子函式中要使用的那個單豎槓或,為什麼不用雙豎槓的或,因為單豎槓的或是位或,就是說左右兩部分都需要被計算,然後再或,C++這裡將true當作1,false當作0,然後進行Bit OR 運算。不能使用雙豎槓或的原因是,如果是雙豎槓或,一旦左半邊為true了,整個就直接是true了,右半邊就不會再計算了,這樣的話,一旦右子樹中有值相同的子樹也不會被計算到結果res中了,參見程式碼如下:

解法二:

class Solution {
public:
    int countUnivalSubtrees(TreeNode* root) {
        int res = 0;
        isUnival(root, -1, res);
        return res;
    }
    bool isUnival(TreeNode* root, int val, int& res) {
        if (!root) return true;
        if (!isUnival(root->left, root->val, res) | !isUnival(root->right, root->val, res)) {
            return false;
        }
        ++res;
        return root->val == val;
    }
};

我們還可以變一種寫法,讓遞迴函式直接返回以當前節點為根的相同值子樹的個數,然後引數裡維護一個引用型別的布林變數,表示以當前節點為根的子樹是否為相同值子樹,我們首先對當前節點的左右子樹分別呼叫遞迴函式,然後把結果加起來,我們現在要來看當前節點是不是和其左右子樹節點值相同,當前我們首先要確認左右子節點的布林型變數均為true,這樣保證左右子節點分別都是相同值子樹的根,然後我們看如果左子節點存在,那麼左子節點值需要和當前節點值相同,如果右子節點存在,那麼右子節點值要和當前節點值相同,若上述條件均滿足的話,說明當前節點也是相同值子樹的根節點,返回值再加1,參見程式碼如下:

解法三:

class Solution {
public:
    int countUnivalSubtrees(TreeNode* root) {
        bool b = true;
        return isUnival(root, b);
    }
    int isUnival(TreeNode *root, bool &b) {
        if (!root) return 0;
        bool l = true, r = true;
        int res = isUnival(root->left, l) + isUnival(root->right, r);
        b = l && r && (root->left ? root->val == root->left->val : true) && (root->right ? root->val == root->right->val : true);
        return res + b;
    }
};

上面三種都是令人看得頭暈的遞迴寫法,那麼我們也來看一種迭代的寫法,迭代寫法是在後序遍歷Binary Tree Postorder Traversal的基礎上修改而來,我們需要用set來儲存所有相同值子樹的根節點,對於我們遍歷到的節點,如果其左右子節點均不存在,那麼此節點為葉節點,符合題意,加入結果set中,如果左子節點不存在,那麼右子節點必須已經在結果set中,而且當前節點值需要和右子節點值相同才能將當前節點加入結果set中,同樣的,如果右子節點不存在,那麼左子節點必須已經存在set中,而且當前節點值要和左子節點值相同才能將當前節點加入結果set中。最後,如果左右子節點均存在,那麼必須都已經在set中,並且左右子節點值都要和根節點值相同才能將當前節點加入結果set中,其餘部分跟後序遍歷的迭代寫法一樣,參見程式碼如下:

解法四:

class Solution {
public:
    int countUnivalSubtrees(TreeNode* root) {
        set<TreeNode*> res;
        if (!root) return 0;
        stack<TreeNode*> s;
        s.push(root);
        TreeNode *head = root;
        while (!s.empty()) {
            TreeNode *t = s.top(); 
            if ((!t->left && !t->right) || t->left == head || t->right == head) {
                if (!t->left && !t->right) {
                    res.insert(t);
                } else if (!t->left && res.find(t->right) != res.end() && t->right->val == t->val) {
                    res.insert(t);
                } else if (!t->right && res.find(t->left) != res.end() && t->left->val == t->val) {
                    res.insert(t);
                } else if (t->left && t->right && res.find(t->left) != res.end() && res.find(t->right) != res.end() && t->left->val == t->val && t->right->val == t->val) {
                    res.insert(t);
                }
                s.pop();
                head = t;
            } else {
                if (t->right) s.push(t->right);
                if (t->left) s.push(t->left);
            }
        }
        return res.size();
    }
};

類似題目:

參考資料: