strategies for trees
https://www.youtube.com/watch?v=s2Yyk3qdy3o&t=140s
Same Tree
Given the roots of two binary trees p and q, write a function to check if they are the same or not.
Two binary trees are considered the same if they are structurally identical, and the nodes have the same value.
time: O(min(n, m)), it would be the min because worst case the smallest one will fail and we end the search
space: O(n+m), best case it would be log(n) since the tree would be balanced. worst case is we traverse each tree fully
class Solution:
invert a binary tree
class Solution:
calculate height of a binary tree, height of an n-ary tree
binary tree
n-ary
Unique Binary Search Trees
Given an integer n, return the number of structurally unique BST’s (binary search trees) which has exactly n nodes of unique values from 1 to n.
BONUS: this is a Catalan number, meaning that C0=1, and Cn+1 = (2(2n+1)/(n+2) * Cn
so you can just do that too, loop from 0,n since catalan numbers start at index 0
time: memoized it is O(n), else it is 2^n exponential, each n spawns 2 more of itself
space: linear, o(n), size of map
class Solution:
Unique Binary Search Trees 2
Given an integer n, return all the structurally unique BST’s (binary search trees), which has exactly n nodes of unique values from 1 to n. Return the answer in any order.
Input: n = 3
Output: [[1,null,2,null,3],[1,null,3,2],[2,1,3],[3,1,null,null,2],[3,2,null,1]]
Example 2:
Input: n = 1
Output: [[1]]
Understand the first problem! write it out on paper to really understand how this makes sense. know bst are catalan numbers: C0=1, C n+1 = (2*(2n+1))/(n+2)) * Cn
time: Catalan number time complexity is O(4^n / n^1/2). this is because Catalan numbers grow as n* Gn (Gn is catalan at n), n* Gn = O(4^n / n^1/2)
space: n* Gn = O(4^n / n^1/2), n=3 it is 35, n=4 is 414, think of the response node and how big it will be, the space is equal to the time complexity since each iteration adds 1 to the list
time if you dont know catalan: If you have a tree with branching of b and max depth of m, the total number of nodes in this tree in worst case: 1 + b + b^2 + … + b^(m-1), which is a sum of geometric sequence: (b^m-1)/(b-1). So you can say that time complexity is O(b^m). For the example above it will be O(n*2^n). It is not exact as a Catalan number but I think it is enough in an interview.
class Solution:
validate a binary search tree
time: O(n), linear, we search through each node
space: O(n), linear, the recursive calls on the stack will be equal to the number of nodes
class Solution:
----def answer(self, node, _min=float("-inf"), _max=float("inf")):
--------if not node:
------------return True
--------val = node.value
if val is less than mid and greater than max,
----return true
default return falsepostorder traversal
post order, meaning post fact, reverse the preorder!
Left, right, root
inorder traversal
INorder, INFINITE, WHILE TRUE:
left, root, right
preorder traversal
root, left, right
binary tree level order traversal, return each level as a list
return list[list[int]]
time: O(n), length of the binary tree
space: N length of output value and the stack would be O(D) where D is the diameter of the tree (largest length of a level)
right view of binary tree
print the right view of the binary tree
time: O(n), we have to see all the nodes in most cases
space: O(D) where D is the diameter of the node, on a balanced tree it would be N/2. O(D) space bc we have to hold each level in the array at a time, which is the diameter
class Solution:
time: O(n), we have to see all the nodes in most cases
space: O(H) where H is the height of the tree since the recursive stack will not go further than the height
time: O(n), we have to see all the nodes in most cases
space: O(D) where D is the diameter of the node, on a balanced tree it would be N/2
Largest Smaller BST Key
Given a root of a Binary Search Tree (BST) and a number num, implement an efficient function findLargestSmallerKey that finds the largest key in the tree that is smaller than num. If such a number doesn’t exist, return -1. Assume that all keys in the tree are nonnegative.
Analyze the time and space complexities of your solution.
For example:
For num = 17 and the binary search tree below:
brute force is to do a BST for a key which is num-1 and make output= max(previous, current). Or each time you find a new number that meets the condition, do the same
time: O(n) at worst, best log(N) if we have a balanced tree
space: recursion stack is O(n) or log(n) at best
def find_largest_smaller_key(self, num): ----return self.helper(num, self.root, -1)
def helper(self, num, current, output):
merge two binary trees
You are given two binary trees root1 and root2.
Imagine that when you put one of them to cover the other, some nodes of the two trees are overlapped while the others are not. You need to merge the two trees into a new binary tree. The merge rule is that if two nodes overlap, then sum node values up as the new value of the merged node. Otherwise, the NOT null node will be used as the node of the new tree.
Return the merged tree.
Note: The merging process must start from the root nodes of both trees.
time: O(min(n, m)), since if root1 is large and root2 is one node, after we merge the first node we will return the remainder of the list
space: O(min(n, m)) bc of the recursive stack or iterative stack. Best case is log(n).
class Solution:
——–return root1
level order of an n-ary tree
easy solution is iterative BFS, which is O(n) time and space, the space is because of the response and in the worst case, a perfect tree’s base level with have n//2 nodes, which means the level array is equal to N space in those cases
time: O(n), linear bc we see all the nodes
space: O(log(n)) best case if it is a perfect tree since recursion only goes down the height of the tree, worst case is O(n)
class Solution:
You are given a binary tree in which each node contains an integer value. The integer value can be positive or negative.
Write a function that counts the number of paths in the binary tree that sum to a given value (the value will be provided as a function parameter).
The path does not need to start or end at the root or a leaf, but it must only go downwards.
brute is to do a preorder traversal at each node assuming it is the root, then calculate if you find the value, this is n^2 time and N space, or log(n) if it is a balanced tree
time: O(n), we traverse the tree once
space: O(n), recursive stack and the prefix sum map. both are linear, the recursive stack could be log(n) if balanced tree
class Solution:
——–self.count += self.prefix_sum[self.carry - self.target]
——–self.prefix_sum[self.carry] += 1
——–if root.left:
————self.pre_order(root.left)
——–if root.right:
————self.pre_order(root.right)
# if we make carry a local method variable, then we do not have to do self.carry -= root.val, this is only necessary bc it is global so once it hits the end of the tree we need to do some backtracking
# We will always need to adjust the prefix sum, however
——–self.prefix_sum[self.carry] -= 1
——–self.carry -= root.val
balanced binary tree (height balanced)
Given a binary tree, determine if it is height-balanced.
For this problem, a height-balanced binary tree is defined as:
a binary tree in which the left and right subtrees of every node differ in height by no more than 1.
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: true
time: O(n), we have to check each node in the tree
space: O(n), could be logN if the tree is perfectly balanced
class Solution:
symmetric tree
Given the root of a binary tree, check whether it is a mirror of itself (i.e., symmetric around its center).
Example 1:
Input: root = [1,2,2,3,4,4,3]
Output: true
time: O(n), we see every node
space: O(n), logN if we have a balance tree, but we could have linked-list style tree
class Solution:
Given the root of a binary tree, return the sum of all left leaves. (root is not a leaf node)
Example 1:
Input: root = [3,9,20,null,null,15,7]
Output: 24
Explanation: There are two left leaves in the binary tree, with values 9 and 15 respectively.
Morris tree traversal is the most advanced (iterative method) but I didn’t learn it so idfk
time: O(n), we go through all the entire list of nodes
space: O(n), recursive space. we could do it iteratively too. with some sort of queue I think
class Solution: