Trees
March 01, 2021
Leetcode problems using tree data structures.
94. Binary Tree Inorder Traversal - Left, Node, Right
I: root = [1,null,2,3]
O: [1,3,2]
- We create an empty results array (
arr) - We create a stack an append the
root - While our stack exists we take
rootand assign it to ournodevariable for assessing - Check if node exists
- Check is it’s an instance of our TreeNode sub-class
- Then we append our
node’s right child, the currentnode’s value, and then thenode’s left child- we add to the stack in the opposite way that the traversal method is assessed
- when we pop the nodes off the stack they’ll then be in order, so must be added in reversed
- LIFO = Last In, First Out
- Else case once we reach a null left
- we will add the node, previously added to our stack, and then append to our output array (
res)
- we will add the node, previously added to our stack, and then append to our output array (
- Return our
resarray
def inorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = [root]
while stack:
node = stack.pop()
if node:
if isinstance(node, TreeNode):
stack.append(node.right)
stack.append(node.val)
stack.append(node.left)
else:
res.append(node)
return res| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit each node once |
| Space | O(h) | bound to height of tree |
100. Same Tree
I: p = [1,2,3], q = [1,2,3]
O: True
- We check our base cases:
- if there’s no
pnode and there’s notqnode it’sTrue- they’re both equally empty and therefore technically the same
- if there’s no
pnode OR there’s notqnode it’sFalse - then we check the value of
p’s root andq’s root, if the values aren’t the same it’sFalse
- if there’s no
- After those 3 checks, we recursively call the function to run these 3 checks on again on:
p’s next left node againstq’s next left nodep’s next right node againstq’s next right node
- This will end when we run out of nodes and we’ll end with either a
TrueorFalsevalue
def isSameTree(self, p: TreeNode, q: TreeNode) -> bool:
if not p and not q:
return True
if not q or not p:
return False
if p.val != q.val:
return False
return isSameTree(p.left, q.left) and isSameTree(p.right, q.right)| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit each node once |
| Space | O(log(n)) / O(n) | best case of balanced tree / worst case |
101. Symmetric Tree
I: [1,2,2,3,4,4,3]
O: True
- We use a helper function and pass the first left & right node to it to check our base cases:
- if there’s no
leftNodenode and there’s notrightNodenode it’sTrue - if there’s no
leftNodenode OR there’s notrightNodenode it’sFalse
- if there’s no
- After those checks, we recursively call this helper function to run these checks again on:
leftNode’s left node againstrightNode’s right nodeleftNode’s right node againstrightNode’s left node- but we verify first that the next node’s values are the same
- This will end when we run out of nodes and we’ll end with either a
TrueorFalsevalue
def treeHelper(leftNode, rightNode):
if not leftNode and not rightNode: return True
if not leftNode or not rightNode: return False
return (leftNode.val == rightNode.val) and
treeHelper(leftNode.left, rightNode.right) and
treeHelper(rightNode.left, leftNode.right)
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
return treeHelper(root.left, root.right)| Big O | Why | |
|---|---|---|
| Time | O(n) | we traverse the tree only once |
| Space | O(n) | recursive method bound by tree’s height |
102. Binary Tree Level Order Traversal - (Breadth First Search)
I: root = [3,9,20,null,null,15,7]
O: [[3],[9,20],[15,7]]
- Initialize an empty
resultarray for the final result - Create a queue from Python’s
Colletionsdata structure containter - Initialize the queue by adding the root
- Begin the while loop, so while the queue is not empty we:
- Create a
qLenvariable equal tolen(q)to ensure we iterate 1 level at a time - Create an empty
levelarray for the current-level node we’ll add to it - Next we’ll iterate over the range of nodes on each level, popping the left once first
- If there’s a node (i.e. not null) we:
- append that node’s value to our
levelarray - add our left child node and right child node to our queue
- append that node’s value to our
- Then, we check if a
leveleven exists in our tree (this accounts for null nodes) before finally appending it to our finalresultarray
- Create a
- Finally, we return our final
resultarray
def levelOrder(self, root: TreeNode) -> List[List[int]]:
res = [] # array for result
q = collections.deque() # import queue (double-ended queue)
q.append(root) # add root to queue
while q: # while queue is non-empty
qLen = len(q) # ensures we iterate 1 level at a time
level = [] # with nodes from that level, we add to their own list
for i in range(qLen): # we add the items from all our lists to the result list for totla
node = q.popleft() # First In First Out
if node: # check that its not null
level.append(node.val) # add node to our curr-level array
q.append(node.left) # add its children to be added next
q.append(node.right) # add its children to be added next
if level: # accounts for null nodes
res.append(level) # after we've processed all nodes on level, add our level results to our result array
return res| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit every node once |
| Space | O(n) | queue at any given time could have up to n/2 elements, which rounds to O(n) |
108. Convert Sorted Array to Binary Search Tree
I: nums = [-10,-3,0,5,9]
O: [0,-3,9,-10,null,5]
- We check if the array
numsexists - Then we set a midpoint by diving the length of our array and diving it by 2
- we use
//in Python because we want an integer (no decimal points)
- we use
- Now our
nodeis set to thismidtree node - We recursively call our function with the left child being everything to the left of the
midnode- and the right being everything to the right of the
mid, starting at 1 node aftermid, to not include it
- and the right being everything to the right of the
- Return
node
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
if not nums:
return None
mid = len(nums)//2
node = TreeNode(nums[mid])
node.left = self.sortedArrayToBST(nums[:mid])
node.right = self.sortedArrayToBST(nums[mid+1:])
return node| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit each node once |
| Space | O(nlog(n)) | recursion is O(n) but array-splitting is O(log(n)) so total recursion tree is n * log(n) |
111. Minimum Depth of Binary Tree
I: root = [3,9,20,null,null,15,7]
O: 2
- We check if there’s a root to begin with
- Next we check if we’re dealing with a skewed tree
- if we are then we return the max depth of this skewed tree
- we only have one “wing” so the min is the max (nothing to compare it to)
- if not a skewed tree (i.e. a regular tree), then we add 1 on each function call and recursively call the function passing the next left and right node to it.
- we return that number
def minDepth(self, root: TreeNode) -> int:
if not root: return 0
if not root.left or not root.right:
return 1+ max(self.minDepth(root.left), self.minDepth(root.right))
return 1+ min(self.minDepth(root.left), self.minDepth(root.right))| Big O | Why | |
|---|---|---|
| Time | O(n) | we traverse each node only once |
| Space | O(h) | recursive call stack for height of tree |
144. Binary Tree Preorder Traversal - Node, Left, Right
I: rroot = [1,null,2,3]
O: [1,2,3]
- We create an empty results array (
arr) - We create a stack an append the
root - While our stack exists we take
rootand assign it to ournodevariable for assessing - Check if node exists
- Check is it’s an instance of our TreeNode sub-class
- Then we append our
node’s right child, thenode’s left child, and then the currentnode’s value- we add to the stack in the opposite way that the traversal method is assessed
- when we pop the nodes off the stack they’ll then be in order, so must be added in reversed
- LIFO = Last In, First Out
- Else case once we reach a null left
- we will add the node, previously added to our stack, and then append to our output array (
res)
- we will add the node, previously added to our stack, and then append to our output array (
- Return our
resarray
def preorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = [root]
while stack:
temp = stack.pop()
if temp:
if isinstance(temp, TreeNode):
stack.append(temp.right)
stack.append(temp.left)
stack.append(temp.val)
else:
res.append(temp)
return res| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit each node once |
| Space | O(h) | bound to height of tree |
145. Binary Tree Postorder Traversal - Left, Right, Node
I: rroot = [1,null,2,3]
O: [1,2,3]
- We create an empty results array (
arr) - We create a stack an append the
root - While our stack exists we take
rootand assign it to ournodevariable for assessing - Check if node exists
- Check is it’s an instance of our TreeNode sub-class
- Then we append our
node’s right child, thenode’s left child, and then the currentnode’s value- we add to the stack in the opposite way that the traversal method is assessed
- when we pop the nodes off the stack they’ll then be in order, so must be added in reversed
- LIFO = Last In, First Out
- Else case once we reach a null left
- we will add the node, previously added to our stack, and then append to our output array (
res)
- we will add the node, previously added to our stack, and then append to our output array (
- Return our
resarray
def postorderTraversal(self, root: TreeNode) -> List[int]:
res = []
stack = [root]
while stack:
temp = stack.pop()
if temp:
if isinstance(temp, TreeNode):
stack.append(temp.val)
stack.append(temp.right)
stack.append(temp.left)
else:
res.append(temp)
return res| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit each node once |
| Space | O(h) | bound to height of tree |
226. Invert Binary Tree
I: [4,2,7,1,3,6,9]
O: [4,7,2,9,6,3,1]
- We check if there’s a root to begin with
- Next we swap the left node with the right node
- Then we recursively call the function with each consecutive left & right node
- invertTree(root.left)
- invertTree(root.right)
- return the root
def invertTree(self, root: TreeNode) -> TreeNode:
if root:
root.left,root.right = root.right, root.left
invertTree(root.left)
invertTree(root.right)
return root| Big O | Why | |
|---|---|---|
| Time | O(n) | we traverse each node only once |
| Space | O(h) | recursive call stack for height of tree |
637. Average Levels of Binary Tree
I: [4,2,7,1,3,6,9]
O: [4,7,2,9,6,3,1]
- Initialize an empty
resultarray for the final result - Create a queue from Python’s
Colletionsdata structure containter - Initialize the queue by addding the root
- Being the while loop, so while the queue is not empty we:
- Create a
qLenvariable equal tolen(q)to ensure we iterate 1 level at a time - Create an empty
levelarray for the current-level node we’ll add to it - Next we’ll iterate over the range of nodes on each level, popping the left once first
- If there’s a node (i.e. not null) we:
- append that node’s value to our
levelarray - add our left child node and right child node to our queue
- append that node’s value to our
- Then, we check if a
leveleven exists in our tree (this accounts for null nodes) before finally appending thelevel’s average (sum of list divided by length of list) to our finalresultarray
- Create a
- Finally, we return our final
resultarray
def averageOfLevels(self, root: TreeNode) -> List[float]:
res = []
q = collections.deque()
q.append(root)
while q:
qLen = len(q)
level = []
for i in range(qLen):
node = q.popleft()
if node:
level.append(node.val)
q.append(node.left)
q.append(node.right)
if level:
res.append(sum(level)/len(level))
return res| Big O | Why | |
|---|---|---|
| Time | O(n) | we visit every node once |
| Space | O(n) | queue at any given time could have up to n/2 elements, which rounds to O(n) |
Written by Christian Turner
Follow me on Twitter