# Leetcode刷題學習筆記--Tree Traversal
## Introduction
ref : [Tree Traversals (Inorder, Preorder and Postorder)](https://www.geeksforgeeks.org/tree-traversals-inorder-preorder-and-postorder/)
![binary tree](https://media.geeksforgeeks.org/wp-content/cdn-uploads/2009/06/tree12.gif)
1. pre-order
順序 : parent -> left -> right, 從top -> bottom
==適用於要先決定好parent才可以決定child。==
**1->2->4->5->3**
![](https://i.imgur.com/tVUSfz6.png)
2. in-order
順序 : left child -> parent -> right child, 從bottom ->top
**4->2->5->1->3**
![](https://i.imgur.com/pasfqjy.png)
==在binary search tree(BST)中,使用inorder可以得到由小到大的排序。==
3. post-order
順序 : left child -> right child -> parent, 從bottom -> top
==適用於要先決定好child才可以決定parent==
**4->5->2->3->1**
![](https://i.imgur.com/NHMcYc3.png)
4. level-order(BFS)
![binary_tree_traversal](https://leetcode.com/problems/validate-binary-search-tree/Figures/145_transverse.png)
## Problems
### [653. Two Sum IV - Input is a BST](https://leetcode.com/problems/two-sum-iv-input-is-a-bst/)
給定一個target,找出在BST中是否有兩個node加起來為target。
想法:
1. 把node全部讀出來放入unordered_set
2. 如果不用額外的空間,只在tree中尋找。(下面code的解法),這個也算是DFS的==pre-order==尋訪。如果是balanced binary tree則效率為O(logN)
```cpp=
TreeNode* findNode(TreeNode *root, int val) {
if(!root) return nullptr;
if(root->val == val) return root;
if(val > root->val) return findNode(root->right, val);
else return findNode(root->left, val);
}
bool helper(TreeNode* root, TreeNode* curr, int k) {
TreeNode* target;
if(!curr) return false;
target = findNode(root, k - curr->val);
if(target && target != curr)
return true;
else
return helper(root, curr->left, k) || helper(root, curr->right, k);
}
bool findTarget(TreeNode* root, int k) {
if(!root) return false;
return helper(root, root, k);
}
```
### [199. Binary Tree Right Side View](https://leetcode.com/problems/binary-tree-right-side-view/)
給一個binary tree只輸出最右邊的value。典型的==level-order traversal==。
```cpp
void helper(vector<TreeNode *>& q, vector<int>& rtn) {
if(q.size() == 0) return;
vector<TreeNode *> newq;
for(TreeNode *node : q) {
if(node->left)
newq.push_back(node->left);
if(node->right)
newq.push_back(node->right);
}
rtn.push_back(q.back()->val);
helper(newq, rtn);
}
vector<int> rightSideView(TreeNode* root) {
if(!root) return {};
vector<int> rtn;
vector<TreeNode *> q;
if(root->left)
q.push_back(root->left);
if(root->right)
q.push_back(root->right);
rtn.push_back(root->val);
helper(q, rtn);
return rtn;
}
```
### [1008. Construct Binary Search Tree from Preorder Traversal(Medium)](https://leetcode.com/problems/construct-binary-search-tree-from-preorder-traversal/)
給你一個用preorder排序過的vector,根據這個vector重建binary tree。
> 1. 第一個數值一定是root
> 2. 因為binary tree的特性是右邊的數值一定大於root,所以找出比第一個數值大的分界點。
> 3. 利用recursive來產生左右邊的sub-tree。
> ps: 可以用使只傳遞上下邊界來改善效能。
```cpp=
TreeNode* bstFromPreorder(vector<int>& preorder) {
auto n = preorder.size();
if(n == 0) return nullptr;
else if(n == 1) return new TreeNode(preorder[0]);
else {
auto pos = upper_bound(preorder.begin() + 1, preorder.end(), preorder[0]);
vector<int> left(preorder.begin() + 1, pos);
vector<int> right(pos, preorder.end());
return new TreeNode(preorder[0],
bstFromPreorder(left),
bstFromPreorder(right));
}
}
```
### [617. Merge Two Binary Trees(Easy)](https://leetcode.com/problems/merge-two-binary-trees/)
給你兩個binary tree, 如果位置一樣的node兩個tree都有,就把node的數值相加,不然就是取有的那個node。
> 1. 重複使用node,避免新增node。
> 2. 把數值都往root1加。
> 3. ==取root->left或root->right的時候,要先判斷root是否為null。==
```cpp=
TreeNode* helper(TreeNode *root1, TreeNode *root2) {
if(!root1 && !root2) return nullptr;
else if(!root1) return root2;
else if(!root2) return root1;
else {
root1->val += root2->val;
return root1;
}
}
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
TreeNode *newroot = helper(root1, root2);
if(newroot) {
newroot->left = mergeTrees(
root1 ? root1->left : nullptr,
root2 ? root2->left : nullptr);
newroot->right = mergeTrees(
root1 ? root1->right : nullptr,
root2 ? root2->right : nullptr);
}
return newroot;
}
```
### [1026. Maximum Difference Between Node and Ancestor(Medium)](https://leetcode.com/problems/maximum-difference-between-node-and-ancestor/)
給你一個binary tree,找出任一node和parent最大的差值。
> 1. 不是一個BST,所以最大最小值不是最頂點的node。
> 2. ==因為差值是取abs,所以必須紀錄最大最小值。==
```cpp=
int helper(TreeNode *node, int maxv, int minv) {
if(!node) return -1;
int rtn = max(abs(maxv - node->val), abs(minv - node->val));
// 紀錄一路過來的最大最小值。因為差值是取abs
maxv = max(maxv, node->val);
minv = min(minv, node->val);
return max(rtn,
max(helper(node->left, maxv, minv),
helper(node->right, maxv, minv)));
}
int maxAncestorDiff(TreeNode* root) {
return max(helper(root->left, root->val, root->val),
helper(root->right, root->val, root->val));
}
```
### [572. Subtree of Another Tree(Easy)](https://leetcode.com/problems/subtree-of-another-tree/)
給你兩個binary tree(root, subRoot),判斷root中是否有subtree和subRoot一樣。
> 1. 問題如果是問兩顆tree一不一樣,問題就簡單多了,只要用下面的isSameTree()來判斷。
> 2. 但是問題是問在root中的subtree,所以尋訪整個tree,如果第一的node數值一樣,就使用isSameTree()來判斷是不是一樣。
```cpp=
bool isSameTree(TreeNode *root, TreeNode *subRoot) {
if(!root && !subRoot) return true;
else if(!root || !subRoot) return false;
else {
if(root->val == subRoot->val)
return isSameTree(root->left, subRoot->left) &&
isSameTree(root->right, subRoot->right);
else
return false;
}
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if(!root) return false;
else if(isSameTree(root, subRoot))
return true;
else
return isSubtree(root->left, subRoot) ||
isSubtree(root->right, subRoot);
}
```
### [1022. Sum of Root To Leaf Binary Numbers(Easy)](https://leetcode.com/problems/sum-of-root-to-leaf-binary-numbers/)
給你一個binary,從root到leaf可以表示成一個binary數值,求出所有binary數值的和。
> 1. 所有進入helper()退出來的時候必須把原本加進去的數值退掉。
```cpp=
void helper(TreeNode *root, int& sum, int& cur) {
cur = (cur << 1) + root->val;
if(!root->left && !root->right) {
sum += cur;
return;
}
if(root->left) {
helper(root->left, sum, cur);
cur = cur >> 1;
}
if(root->right) {
helper(root->right, sum, cur);
cur = cur >> 1;
}
}
int sumRootToLeaf(TreeNode* root) {
int sum{0}, cur{0};
helper(root, sum, cur);
return sum;
}
```
### [236. Lowest Common Ancestor of a Binary Tree(Medium)](https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-tree/)
給你一個binary tree和兩個node(p, q)。找出p, q共同最低的祖先node。
> 1. 如果root為共同的祖先,則可以在左半邊和右半邊找到p或q。
![](https://i.imgur.com/FHWziiq.png)
> 2. 如果root不為共同祖先,則只會在一邊找到p和q,另一邊找不到。那就往non-null的那邊繼續找。
![](https://i.imgur.com/kUOJHxZ.png)
```cpp=
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
// 找到p或q, return node
if(!root || root == p || root == q) return root;
// 左邊是否有p或q
TreeNode *left = lowestCommonAncestor(root->left, p, q);
// 右邊是否有p或q
TreeNode *right = lowestCommonAncestor(root->right, p, q);
// 左右邊都有,所以這是共同的root
if(left && right) return root;
// 否則選有的那一邊
return left ? left : right;
}
```
2022/07/05
> 1. 為什麼這題是使用post-order?
> 2. 因為我==必須先知道child的狀態==,才可以決定parent是不是LCA。
2024/02/09
還是參考了之前的解答。
```cpp=
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(!root || root == p || root == q) return root; // child狀態
TreeNode* l = lowestCommonAncestor(root->left, p, q);
TreeNode* r = lowestCommonAncestor(root->right, p, q);
// 得到兩邊child狀態之後再來判斷
if(l && r) return root;
else return l ? l : r;
}
```
### [968. Binary Tree Cameras](https://leetcode.com/problems/binary-tree-cameras/)
一台camera可以看到自己和上下的node,試問最少的camera可以覆蓋所有的node。
> 1. 一個node有三種狀態,放置camera,有被camera監視,沒有被camera監視。
> 2. 如果top-down,就必須考慮node的所有可能,還要根據root node狀態推斷child node可能的狀態,比較困難。
> 3. 如果bottom-up的話,因為bottom狀態確定了,比較容易推斷root node的狀態,所以使用bottom-up。
> 4. 既然是bottom-up,就是post-order,先走訪到最底部才開始判斷前一個的狀態。
```cpp=
class Solution {
enum state {mon, nomon, camera};
int cnt;
int helper(TreeNode *root) {
if(!root) return mon; // 把null node視為monitor node,因為不用管他
int l = helper(root->left);
int r = helper(root->right);
if(l == nomon || r == nomon) { // child有一個沒被monitor, 放置camera, case1
cnt++;
return camera;
} else if(l == mon && r == mon) // case2
return nomon;
return mon; // child 有一個有camera, case 3
// left, right
// mon, mon case 2, leaf
// mon, nomon case 1
// mon, camera case 3
// nomon, mon case 1
// nomon, nomon case 1
// nomon, camera case 1
// camera, mon case 3
// camera, nomon case 1
// camera, camera case 3
}
public:
int minCameraCover(TreeNode* root) {
int val = helper(root);
return (val == nomon ? 1 : 0) + cnt; // 因為root沒被monitor,所以放置一個camera
// time complexity : O(N)
// space complexity : O(N)
}
};
```
### [199. Binary Tree Right Side View(Medium)](https://leetcode.com/problems/binary-tree-right-side-view/)
列出一個binary tree每個level最右邊node的數值。
> 1. 使用preorder traversal,但是先走訪右邊再走訪左邊。因為我們想要先拿到右邊的node。
> 2. 使用level來記錄目前走到的level,因為每一層只有一個right-most node,所以當rtn.size() < level我們才紀錄。
```cpp=
class Solution {
void helper(TreeNode *root, int level, vector<int>& rtn) {
if(!root) return;
if(rtn.size() < level) rtn.push_back(root->val);
helper(root->right, level + 1, rtn);
helper(root->left, level + 1, rtn);
}
public:
vector<int> rightSideView(TreeNode* root) {
vector<int> rtn;
helper(root, 1, rtn);
return rtn;
}
};
```
### [110. Balanced Binary Tree](https://leetcode.com/problems/balanced-binary-tree/)
判斷binary tree是否為balanced。balanced的定義為從root來看左子樹和右子樹的高度差不為1。
> 1. 因為要先知道左右兩邊的高度,所以為post-order。
> 2. 得到兩邊子樹的高度之後,如果不是balanced那就不需要再判斷直接回傳-1。
> 3. 如果兩邊差大於1也是回傳-1。
> 4. 最後回傳計算後的高度。
```cpp=
int helper(TreeNode *root) {
if(!root) return 0;
int hofl = helper(root->left);
int hofr = helper(root->right);
if(hofl == -1 || hofr == -1 || abs(hofl - hofr) > 1) return -1;
return max(hofl, hofr) + 1;
}
bool isBalanced(TreeNode* root) {
return helper(root) == -1 ? false : true;
}
```
### [437. Path Sum III](https://leetcode.com/problems/path-sum-iii/)
找到在binary tree中的path剛好總合為target。這邊的path的定義是從parent到child。不會有child->parent->child的路徑。
> 1. 每個node都可以為起始點,所以使用line 13, line 14,把path總和算是每個node的起點。
> 2. 使用helper來計算以root為起點的path總和。
> 3. 在helper中經過的每個node都為終點,所以計算總合。line 3。
> 4. 如果以這個點的總和為target則加1,line 4。
> 5. 繼續統計以root->left和root->right為終點的path。line 5,6。
> 6. 因為有line 13, 14,所以不必計算單點是否等於target。
```cpp=
int helper(TreeNode *root, long pre) {
if(!root) return 0;
long cur = pre + root->val;
return (cur == target) +
helper(root->left, cur) +
helper(root->right, cur);
}
int target;
int pathSum(TreeNode* root, int targetSum) {
if(!root) return 0;
target = targetSum;
return helper(root, 0) +
pathSum(root->left, targetSum) + // 從 root->left開始,所以不用判斷每個點是否為traget
pathSum(root->right, targetSum); // 從 root->right開始
}
```
### [2246. Longest Path With Different Adjacent Characters](https://leetcode.com/problems/longest-path-with-different-adjacent-characters/description/)
找出tree中,最長的path其中每兩個相鄰的node的char都不一樣。
> 1. 一開始使用了DFS結果TLE。
> 2. 參考了lee215大的解法,發現這題和[124. Binary Tree Maximum Path Sum](https://leetcode.com/problems/binary-tree-maximum-path-sum/)蠻像的。
> 3. 應該說只要是在tree中找path應該都是一樣的套路。
> 4. path至少會有一個node,
> 5. 兩個以上的node就是會跟child連在一起。
> 6. 有可能跟一個child連在一起,也有可能跟兩個child連在一起。
> 7. 如果是跟兩個child連在一起。那一定是最長的兩個child。
> 8. why preorder? 因為必須先知道每個child最長的path。
> 9. 根據s[cur] != s[child],才可以判斷要不要取用這條path。所以判斷必須在pre-order之後。
> 10. 回傳只能傳回node + 最長的child。
```cpp
class Solution {
// why pre-order?
// 因為必須先知道每個child的最長path,
// 根據s[cur] != s[child],才可以判斷可不可以取用這條path,
// 必須取出兩條最長的child path就可以拼成更長的Path
int maxans{0};
int dfs(unordered_map<int, vector<int>>& m, int cur, string& s, int len) {
int big1{0}, big2{0};
for(auto& child : m[cur]) {
int rtn = dfs(m, child, s, len + 1); // 從目前node往下的path長度
if(s[cur] == s[child]) continue;
if(rtn > big2) big2 = rtn;
if(big2 > big1) swap(big1, big2);
}
maxans = max(maxans, big1 + big2 + 1); // 經過node最長的path由node和兩個最長的邊組成
return big1 + 1; // 最長一條path
}
public:
int longestPath(vector<int>& parent, string s) {
unordered_map<int, vector<int>> m; // node, adj node
for(int i = 1; i < parent.size(); ++i)
m[parent[i]].push_back(i);
dfs(m, 0, s, 0);
return maxans;
}
};
```
### [958. Check Completeness of a Binary Tree](https://leetcode.com/problems/check-completeness-of-a-binary-tree/description/)
給你一個TreeNode* root,試判斷是否為complete binary tree。
> 1. 一開始一直在想到底是要用pre-order, in-order或是post-order,就是忘了想用level order。
> 2. 另一個解法是把每個node都給上一個value,最後排序這個vector但是會int overflow。
> 3. 根據 complete binary tree的特性,如果我用level order traversal,就不會在node之前先遇到nullptr。
> 4. 如果是complete binary tree最後的queue內容都會是nullptr。
```cpp=
bool isCompleteTree(TreeNode* root) {
queue<TreeNode*> q{{root}};
// 遇到nullptr就停止。
while(q.front() != nullptr) {
TreeNode* cur = q.front(); q.pop();
q.push(cur->left);
q.push(cur->right);
}
// 如果是complete binary tree的話,最後都會是nullptr
while(!q.empty() && q.front() == nullptr) q.pop();
return q.empty();
}
```
### [979. Distribute Coins in Binary Tree](https://leetcode.com/problems/distribute-coins-in-binary-tree/description/)
binary tree中每個node都有不同的coins,找出最少的移動可以使每個node都有一個coin。
> 1. 參考官方解答。
> 2. why post-order traversal?
> + 必須知道需要往child移動多少的coins,或是必須知道要從child移動多少coins上來parent。
> + 正數表示child有多的coins必須移動上來。
> + 負數表示child有少,必須從parent動到child。
> 3. 因為child移動到child都要經過parent,所以可以視為多的先從child移動到parent,parent再分配給child。
> 4. 也就是parent是一個中央統籌單位,多的coins先交給中央parent,再由中央parent分配給不夠的child。
> 5. 如果整體來說還是不夠,中央parent再跟更上面的parent要求更多的coins。
> 6. 如果分配完了還有多出來,中央parent再把剩下的往更上的parent繳交出去。
```cpp=
class Solution {
int ans{};
// child多的coin,必須從child移動到root
// child缺的coin, 必須從root移動到child
int dfs(TreeNode* root) { // + : child->root, - : root->child
if(!root) return 0;
int l = dfs(root->left);
int r = dfs(root->right);
ans += abs(l) + abs(r);
return root->val + l + r - 1;
}
public:
int distributeCoins(TreeNode* root) {
dfs(root);
return ans;
}
};
```
### [2477. Minimum Fuel Cost to Report To the Capital](https://leetcode.com/problems/minimum-fuel-cost-to-report-to-the-capital/description/)
每個node除了0之外,都要往node 0前進,每前進一個node需要1L fuel。一台車子最多有seats個椅子。試問,最少的fuel來讓所有的node都達到node 0。
> 1. 如果有n個人,則需要多少台車子? 使用無條件進位ceil。
```cpp
ceil(n / s);
```
> 2. 所以使用post-order,因為要到達最後的leaf node,再往node 0前進,推算有多少個node在後面。
> 3. 使用vector<int> vis來記錄訪問過的node。
> 4. 為什麼統計每個node的車子數,就是統計需要的fuel?
![image](https://hackmd.io/_uploads/Bk1hKcpSp.png)
```cpp=
class Solution {
unordered_map<int, vector<int>> adj;
vector<int> vis; // 使用visited array來避免重複走
long long ans;
int dfs(int n, int s) {
vis[n] = 1;
int count = 1;
//統計從child過來的node有多少個
for(auto& nxt : adj[n]) {
if(!vis[nxt])
count += dfs(nxt, s);
}
if(n != 0)
ans += ceil((double)count / s); //站在每個node,會有幾台車經過
// ans += ((rtn + seats - 1) / seats);
// 不使用ceil的無條件進位
return count; // 有多少個node
}
public:
long long minimumFuelCost(vector<vector<int>>& roads, int seats) {
for(auto& r : roads) {
adj[r[0]].push_back(r[1]);
adj[r[1]].push_back(r[0]);
}
vis.resize(roads.size() + 1);
dfs(0, seats);
return ans;
}
};
```
###### tags: `leetcode` `刷題`