[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子樹的題
解法一:
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(); } };
類似題目:
參考資料: