Implement topological sort and tree boundary traversal
Company: Snowflake
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: These tasks evaluate core competencies in directed graph algorithms (topological ordering and cycle detection) and binary tree traversals (boundary identification and leaf enumeration), testing understanding of dependency modeling and traversal patterns.
Part 1: Order Courses With Prerequisites
Constraints
- 1 <= n <= 100000
- 0 <= len(prerequisites) <= 200000
- 0 <= course, prerequisite < n for every prerequisite pair
- The graph may be disconnected
- The graph may contain cycles
Examples
Input: (1, [])
Expected Output: [0]
Explanation: A single course with no prerequisites can be taken immediately.
Input: (4, [[1, 0], [2, 0], [3, 1], [3, 2]])
Expected Output: [0, 1, 2, 3]
Explanation: Course 0 must come before 1 and 2, and both 1 and 2 must come before 3.
Input: (2, [[0, 1], [1, 0]])
Expected Output: []
Explanation: Course 0 depends on 1 and course 1 depends on 0, forming a cycle.
Input: (5, [[1, 0], [3, 2]])
Expected Output: [0, 1, 2, 3, 4]
Explanation: There are two independent prerequisite chains and one isolated course.
Input: (3, [])
Expected Output: [0, 1, 2]
Explanation: With no prerequisites, all courses are immediately available.
Solution
def solution(n, prerequisites):
import heapq
graph = [[] for _ in range(n)]
indegree = [0] * n
for course, prereq in prerequisites:
graph[prereq].append(course)
indegree[course] += 1
available = [course for course in range(n) if indegree[course] == 0]
heapq.heapify(available)
order = []
while available:
course = heapq.heappop(available)
order.append(course)
for next_course in graph[course]:
indegree[next_course] -= 1
if indegree[next_course] == 0:
heapq.heappush(available, next_course)
if len(order) != n:
return []
return orderTime complexity: O((n + m) log n), where m is len(prerequisites), because each course may be pushed/popped from the heap and each edge is processed once.. Space complexity: O(n + m) for the adjacency list, indegree array, heap, and result..
Hints
- Track how many prerequisites each course still has using an indegree array.
- Courses with indegree 0 can be taken immediately; if you cannot process all courses, a cycle exists.
Part 2: Boundary Traversal of a Binary Tree
Constraints
- 0 <= len(tree) <= 100000
- -1 is reserved as the missing-child sentinel
- All actual node values are non-negative integers
- The input list is a valid standard level-order encoding of a binary tree
Examples
Input: ([],)
Expected Output: []
Explanation: An empty tree has an empty boundary.
Input: ([10],)
Expected Output: [10]
Explanation: A single-node tree contains only the root.
Input: ([1, 2, 3, 4, 5, 6, 7],)
Expected Output: [1, 2, 4, 5, 6, 7, 3]
Explanation: The left boundary is 2, the leaves are 4, 5, 6, 7, and the right boundary bottom-up is 3.
Input: ([1, -1, 2, 3, 4],)
Expected Output: [1, 3, 4, 2]
Explanation: The root has only a right child. Leaves 3 and 4 are listed before the non-leaf right boundary node 2.
Input: ([1, 2, 3, -1, 5, -1, 4],)
Expected Output: [1, 2, 5, 4, 3]
Explanation: The boundary follows missing-child paths correctly: left boundary 2, leaves 5 and 4, then right boundary 3.
Solution
def solution(tree):
from collections import deque
if not tree or tree[0] == -1:
return []
class Node:
__slots__ = ('val', 'left', 'right')
def __init__(self, val):
self.val = val
self.left = None
self.right = None
root = Node(tree[0])
queue = deque([root])
index = 1
while queue and index < len(tree):
node = queue.popleft()
if index < len(tree):
if tree[index] != -1:
node.left = Node(tree[index])
queue.append(node.left)
index += 1
if index < len(tree):
if tree[index] != -1:
node.right = Node(tree[index])
queue.append(node.right)
index += 1
def is_leaf(node):
return node.left is None and node.right is None
result = [root.val]
current = root.left
while current is not None:
if not is_leaf(current):
result.append(current.val)
if current.left is not None:
current = current.left
else:
current = current.right
if not is_leaf(root):
stack = [root]
while stack:
node = stack.pop()
if is_leaf(node):
result.append(node.val)
else:
if node.right is not None:
stack.append(node.right)
if node.left is not None:
stack.append(node.left)
right_boundary = []
current = root.right
while current is not None:
if not is_leaf(current):
right_boundary.append(current.val)
if current.right is not None:
current = current.right
else:
current = current.left
result.extend(reversed(right_boundary))
return resultTime complexity: O(N), where N is the number of entries/nodes represented by the input tree. Each node is built and visited a constant number of times.. Space complexity: O(N) for the constructed tree and auxiliary queues/stacks..
Hints
- Do not add leaves while collecting the left or right boundary; collect all leaves in a separate pass to avoid duplicates.
- Collect the right boundary top-down into a temporary list, then reverse it before appending to the answer.