Skill Set - Tree Breadth First Search


Skills for tackling LeetCode problems - Tree Breadth First Search.

BFS can efficiently solve problems involving the traversal of a tree in a level-by-level order. It is usually implemented with a Queue to keep track of all the nodes of a level before jumping into the next level. Therefore, the space complexity is O(W) where W is the max number of nodes on any level. The time complexity is O(H) where H is the max height of the tree.

1. Binary Tree Level order Traversal

Given a binary tree, populate an array to represent its level-by-level traversal. You should populate the values of all nodes of each level from left to right in separate sub arrays.

# Time complexity O(N), where N is the total number of nodes in the tree. 
# Space complexity O(N), as we need a queue to store values of all nodes.

from collections import deque

# class TreeNode:
#     def __init__(self, val):
#         self.val = val
#         self.left, self.right = None, None

def traverse(root):
    result = []
    if root is None: return result

    queue = deque()
    queue.append(root)

    while queue:
        levelSize = len(queue) # number of nodes on each level
        currentLevel = [] # store values of nodes on each level
        for _ in range(levelSize): # for nodes on each level
            currentNode = queue.popleft()
            currentLevel.append(currentNode.val) # append that node
            if currentNode.left: # push left child of that node into queue
                queue.append(currentNode.left)
            if currentNode.right: # push right child of that node into queue
                queue.append(currentNode.right)

        result.append(currentLevel)
    return result

2. Reverse Level Order Traversal

Given a binary tree, populate an array to represent its level-by-level traversal in reverse order, i.e., the lowest level comes first. You should populate the values of all nodes in each level from left to right in separate sub-arrays.

# Time complexity O(N). Space complexity O(N).
def traverse(root):
    result = deque() # allow appendleft
    if root is None: return result

    queue = deque()
    queue.append(root)
    while queue:
        levelSize = len(queue)
        currentLevel = []
        for _ in range(levelSize):
            currentNode = queue.popleft()
            currentLevel.append(currentNode.val)
            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)
        result.appendleft(currentLevel) # append to the left
    return list(result)

3. Zigzag Traversal

Given a binary tree, populate an array to represent its zigzag level order traversal. You should populate the values of all nodes of the first level from left to right, then right to left for the next level and keep alternating in the same manner for the following levels.

# Time complexity O(N). Space complexity O(N).
def traverse(root):
    result = []
    if root is None: return result
    
    queue = deque()
    queue.append(root)
    flag = True

    while queue:
        levelSize = len(queue)
        currentLevel = deque() # allow appendleft
        for _ in range(levelSize):
            currentNode = queue.popleft()

            # determine the order of nodes in a level
            if flag: 
                currentLevel.append(currentNode.val)
            else:
                currentLevel.appendleft(currentNode.val)
            
            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

        result.append(list(currentLevel))
        flag = not flag

    return result

4. Level Averages in a Binary Tree

Given a binary tree, populate an array to represent the averages of all of its levels.

# Time complexity O(N). Space complexity O(N).
def find_level_averages(root):
    result = []
    if root is None:
        return result
    
    queue = deque()
    queue.append(root)
    while queue: # iterate through each level
        levelSize = len(queue) 
        levelSum = 0.0  # store the sum of values in a level

        for _ in range(len(queue)): # iterate through nodes in a level
            currentNode = queue.popleft()
            levelSum += currentNode.val # add up all values
            
            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

        result.append(levelSum / levelSize) # calcualte the average
    return result

Similar problem: find the largest value on each level of a binary tree.

def find_level_max(root):
    result = []
    if root is None:
        return result
    
    queue = deque()
    queue.append(root)
    while queue: # iterate through each level
        levelSize = len(queue) 
        levelMax = 0  # store the sum of values in a level

        for _ in range(len(queue)): # iterate through nodes in a level
            currentNode = queue.popleft()
            levelMax = max(levelMax, currentNode.val) # find the max value
            
            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

        result.append(levelMax) 
    return result

5. Minimum Depth of a Binary Tree

Find the minimum depth of a binary tree. The minimum depth is the number of nodes along the shortest path from the root node to the nearest leaf node.

# Time complexity O(N). Space complexity O(N).
def find_minimum_depth(root):
    if root is None:
        return 0

    queue = deque()
    queue.append(root)
    MIN = 0

    while queue:
        MIN += 1
        for _ in range(len(queue)):
            currentNode = queue.popleft()

            # check if this is a leaf node
            if not currentNode.left and not currentNode.right:
                return MIN

            # append the childs to the queue
            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

Similar problem: Find the maximum depth.

# Time complexity O(N). Space complexity O(N).
def find_maximum_depth(root):
    if root is None: return 0
    queue = deque()
    queue.append(root)
    MAX = 0

    while queue:
        MAX += 1
        for _ in range(len(queue)):
            currentNode = queue.popleft()

            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

    return MAX

6. Level Order Successor

Given a binary tree and a node, find the level order successor of the given node in the tree. The level order successor is the node that appears right after the given node in the level order traversal.

# Time complexity O(N). Space complexity O(N).
def find_successor(root, key):
    if root is None:
        return None

    queue = deque()
    queue.append(root)

    while queue:
        currentNode = queue.popleft()

        if currentNode.left:
            queue.append(currentNode.left)
        if currentNode.right:
            queue.append(currentNode.right)
        
        if currentNode.val == key: # found, return the first element in queue
            break

    return queue[0] if queue else None

7. Connect Level Order Siblings

Given a binary tree, connect each node with its level order successor. The last node of each level should point to a null node.

# Time complexity O(N). Space complexity O(N).
def connect_level_order_siblings(root):
    if root is None:
        return

    queue = deque()
    queue.append(root)
    while queue:
        pre = None # initiate a pointer
        for _ in range(len(queue)):
            currentNode = queue.popleft()
            
            # connect nodes
            if pre:
                pre.next = currentNode
            pre = currentNode

            if currentNode.left:
                queue.append(currentNode.left)
            if currentNode.right:
                queue.append(currentNode.right)

  TOC