For more on binarytree check out Trees.
Invert Binary Tree - Easy
This is a CLASSIC example of a problem that demonstrates recursion and binarytree understanding.
- As always start by defining your base-case which is the empty node (or tree).
- Then swap the left and right using a temporary node for one of them.
- Then recursively call the function on the right and left branches.
- Return the tree at the end.
My Java implementation (it’s FAST)
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root == null){
return root;
}
TreeNode tempNode = root.right;
root.right = root.left;
root.left = tempNode;
invertTree(root.right;
invertTree(root.left);
return root;
}
}This solution has a time complexity of where is the number of nodes in the tree and space complexity where is the number of levels or the height of the tree.
Maximum Depth of Binary Tree
Find the max depth i,e, height.
maxDepth(root) {
if (root === null){
return 0;
}
let left = 1 + this.maxDepth(root.left);
let right = 1 + this.maxDepth(root.right);
return Math.max(left, right);
}Find Lowest Common Ancestor of BST - Easy
The Lowest Common Ancestor (LCA) refers to the lowest (by height not value) node in the tree that’s a common ancestor.
You’re given a tree and two nodes p and q, and you need to find their LCA.
Sometimes the root is the lowest, or either `p` or `q` in some edge cases.
Algorithm breakdown:
- If root is null or equals the value of either
porqthen it’s the LCA - If values of
pandqare less than root’s, then search left subtree - If values of
pandqare greater than root’s, then search right subtree - Otherwise you’ve found the split, meaning the node is the LCA
Code:
def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
if not root or root.val == p.val or root.val == q.val:
return root
if p.val < root.val and q.val < root.val:
return self.lowestCommonAncestor(root.left, p, q)
elif root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
else:
return rootLowest Common Ancestor of Binary Tree (Not BST) - Medium
This one’s a doozy because I find the LCA definition to be confusing.
The LCA is the lowest node (closest to root) that has both p and q in its subtree.
Obviosly this can be solved using dfs or bfs but the sauce is in the base cases
Note, that this problem isn’t concerned with the values of the nodes. Since it’s not a binarysearchtree , it can duplicate values! // Check for Node references not node values
DFS Algorithm
- Base cases: if root is null or root itself is either
porq, then return root - Recursive call to root’s left subtree followed by a recursive call to its right subtree
- Check if both subtrees are not null, return root
- Otherwise return the one that’s not null
Code:
def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
if not root or root == p or root == q:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left and right:
return root
return right if right else leftBalanced Binary Tree - Easy
A Binary Tree in which the depth of the two subtrees of every node never differs by more than one.
Breakdown
- Get the height of the left and right sub-trees.
Use recursion to get the height, base case is null of course. - Get the absolute height difference
- Check if abs diff is less than or equal to one.
Balanced binary tree
A height-balanced binary tree is a binary tree in which the depth of the two subtrees of every node never differs by more than one.
Naive Top-Down recursive approach
This one involves traversing from the root recursively towards the bottom of the binary tree to get the heights.
Code:
class Solution {
int height(TreeNode root) {
if (root == null) {
return 0;
}
int leftHeight = height(root.left);
int rightHeight = height(root.right);
if (leftHeight > rightHeight) {
return 1 + leftHeight;
}
else {
return 1 + rightHeight;
}
// can do instead: return 1 + Math.max(height(root.left), height(root.right));
}
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
int left = height(root.left);
int right = height(root.right);
if (1 < Math.abs(right - left)){
return false;
}
return isBalanced(root.left) && isBalanced(root.right);
}
}Because we’re traversing recursively through twice, once when we get the height and then when we compare if EVERY sub-tree is balanced) this ends up being and space complexity of .
Better Bottom-Up recursive approach
Instead of doing the recursive traversal twice, in two functions. Instead, we make two calls per sub-tree in one function, and based on their difference return a boolean then and there.
As explained in this Digital Ocean article.
Algorithm:
- If node == null -> return 0
- Check left subtree. If not balanced -> return -1
- Check right subtree. If not balanced -> return -1
- The absolute between heights of left and right subtrees. If it is greater than 1 -> return -1.
- Else, return the 1 + max height between the left and right subtree
- Check if the result is greater than 0.
Code:
class Solution {
public int helper(TreeNode root){
if(root == null){
return 0;
}
int left = helper(root.left);
int right = helper(root.right);
if(left == -1 || right == -1 || (1 < Math.abs(right - left))){
return -1;
}else{
return 1 + Math.max(left, right);
}
}
public boolean isBalanced(TreeNode root) {
if(root == null){
return true;
}
return 0 < helper(root);
}
}This is much faster, because we’re only traversing recursively twice per node in a single function.
Time complexity of and space complexity (i think) is the same .
Same Tree
DFS
def is_same_tree(p, q):
if not p and not q:
return true
elif p and q and p.val == q.val:
return is_same_tree(p.left, q.left) and is_same_tree(p.right, q.right)
else:
return FalseBFS
The scanning of neighbours using the for-loop wasn’t intuitive
def isSameTree(p,q):
fst = deque()
fst.append(p)
sec = deque()
sec.append(q)
while fst and sec
# checks all the neighbours per level
for i in range(len(fst)):
i = fst.popleft()
j = sec.popleft()
# visit and check
if not i and not j:
continue # nothing to return but need to check...
if not i or not j or i.val != j.val: return False
fst.append(i.left)
sec.append(j.left)
fst.append(i.right)
sec.append(j.right)
return TrueLevel Order Traversal - Medium
This is the standard Binary Tree LOT problem.
Given a Binary Tree, return an array of arrays, with each containing the values of the nodes on that level.
For an algorithm breakdown, read Graph and Tree Algorithms > Level Order Traversal.
The only thing to consider is to loop through the level using the size of the queue as the number of nodes per level.
Code:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
Q = deque()
Q.append(root)
order = []
while Q:
lvl_size = len(Q)
lvl = []
for i in range(lvl_size):
cur = Q.popleft()
if cur:
lvl.append(cur.val)
if cur.left: Q.append(cur.left)
if cur.right: Q.append(cur.right)
order.append(lvl)
return order