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.
Example :
Input: root = [5,1,5,5,5,null,5] 5 / \ 1 5 / \ \ 5 5 5 Output: 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 的基礎上修改而來,需要用 HashSet 來保存所有相同值子樹的根節點,對於遍歷到的節點,如果其左右子節點均不存在,那么此節點為葉節點,符合題意,加入結果 HashSet 中,如果左子節點不存在,那么右子節點必須已經在結果 HashSet 中,而且當前節點值需要和右子節點值相同才能將當前節點加入結果 HashSet 中,同樣的,如果右子節點不存在,那么左子節點必須已經存在 HashSet 中,而且當前節點值要和左子節點值相同才能將當前節點加入結果 HashSet 中。最后,如果左右子節點均存在,那么必須都已經在 HashSet 中,並且左右子節點值都要和根節點值相同才能將當前節點加入結果 HashSet 中,其余部分跟后序遍歷的迭代寫法一樣,參見代碼如下:
解法四:
class Solution { public: int countUnivalSubtrees(TreeNode* root) { if (!root) return 0; unordered_set<TreeNode*> res; stack<TreeNode*> st{{root}}; TreeNode *head = root; while (!st.empty()) { TreeNode *t = st.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); } st.pop(); head = t; } else { if (t->right) st.push(t->right); if (t->left) st.push(t->left); } } return res.size(); } };
Github 同步地址:
https://github.com/grandyang/leetcode/issues/250
類似題目:
Binary Tree Postorder Traversal
參考資料:
https://leetcode.com/problems/count-univalue-subtrees/
https://leetcode.com/problems/count-univalue-subtrees/discuss/67602/Java-11-lines-added
https://leetcode.com/problems/count-univalue-subtrees/discuss/67644/AC-clean-Java-solution
https://leetcode.com/problems/count-univalue-subtrees/discuss/67573/My-Concise-JAVA-Solution