Solve tree, graph, sliding-window problems
Company: Google
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This set of problems evaluates proficiency in core data structures and algorithms, specifically binary tree traversals (including zigzag/level-order patterns), directed graph cycle detection and topological ordering, and sliding-window techniques for substring analysis.
Part 1: Binary Tree Zigzag Level Order Traversal
Constraints
- 0 <= len(root) <= 2000
- Each non-None node value is an integer in the range [-10^4, 10^4]
- The input list represents a valid binary tree in level-order form
Examples
Input: ([3, 9, 20, None, None, 15, 7],)
Expected Output: [[3], [20, 9], [15, 7]]
Explanation: Level 1 is read left-to-right, level 2 right-to-left, and level 3 left-to-right.
Input: ([1],)
Expected Output: [[1]]
Explanation: A single-node tree has only one level.
Input: ([],)
Expected Output: []
Explanation: An empty tree has no levels to traverse.
Input: ([1, 2, 3, 4, None, None, 5],)
Expected Output: [[1], [3, 2], [4, 5]]
Explanation: The second level is reversed, while the first and third remain left-to-right.
Solution
def solution(root):
from collections import deque
if not root:
return []
class TreeNode:
def __init__(self, val):
self.val = val
self.left = None
self.right = None
values = iter(root)
first = next(values)
if first is None:
return []
tree_root = TreeNode(first)
build_queue = deque([tree_root])
while build_queue:
node = build_queue.popleft()
try:
left_val = next(values)
except StopIteration:
break
if left_val is not None:
node.left = TreeNode(left_val)
build_queue.append(node.left)
try:
right_val = next(values)
except StopIteration:
break
if right_val is not None:
node.right = TreeNode(right_val)
build_queue.append(node.right)
result = []
bfs_queue = deque([tree_root])
left_to_right = True
while bfs_queue:
level = []
for _ in range(len(bfs_queue)):
node = bfs_queue.popleft()
level.append(node.val)
if node.left:
bfs_queue.append(node.left)
if node.right:
bfs_queue.append(node.right)
if not left_to_right:
level.reverse()
result.append(level)
left_to_right = not left_to_right
return resultTime complexity: O(n). Space complexity: O(n).
Hints
- A breadth-first search naturally processes the tree one level at a time.
- You can collect each level left-to-right, then reverse every other level before appending it to the answer.
Part 2: Course Schedule
Constraints
- 1 <= num_courses <= 10^5
- 0 <= len(prerequisites) <= 2 * 10^5
- Each prerequisite pair has length 2
- 0 <= course, prerequisite < num_courses
Examples
Input: (2, [[1, 0]])
Expected Output: True
Explanation: Course 0 can be taken first, then course 1.
Input: (2, [[1, 0], [0, 1]])
Expected Output: False
Explanation: The two courses depend on each other, forming a cycle.
Input: (1, [])
Expected Output: True
Explanation: With one course and no prerequisites, finishing is always possible.
Input: (4, [[1, 0], [2, 0], [3, 1], [3, 2]])
Expected Output: True
Explanation: A valid order is 0, 1, 2, 3 or 0, 2, 1, 3.
Input: (5, [[1, 0], [0, 1], [3, 4]])
Expected Output: False
Explanation: Courses 0 and 1 form a cycle, so not all courses can be completed.
Solution
def solution(num_courses, prerequisites):
from collections import deque
graph = [[] for _ in range(num_courses)]
indegree = [0] * num_courses
for course, prereq in prerequisites:
graph[prereq].append(course)
indegree[course] += 1
queue = deque([i for i in range(num_courses) if indegree[i] == 0])
finished = 0
while queue:
current = queue.popleft()
finished += 1
for nxt in graph[current]:
indegree[nxt] -= 1
if indegree[nxt] == 0:
queue.append(nxt)
return finished == num_coursesTime complexity: O(num_courses + len(prerequisites)). Space complexity: O(num_courses + len(prerequisites)).
Hints
- Think of courses as nodes in a directed graph and prerequisites as directed edges.
- If you repeatedly take courses with indegree 0 and cannot process all nodes, a cycle exists.
Part 3: Longest Substring with At Most K Distinct Characters
Constraints
- 0 <= len(s) <= 2 * 10^5
- 0 <= k <= len(s)
- s may contain any standard characters
Examples
Input: ("eceba", 2)
Expected Output: 3
Explanation: The longest valid substring is 'ece', which has length 3.
Input: ("aa", 1)
Expected Output: 2
Explanation: The whole string contains only one distinct character.
Input: ("", 3)
Expected Output: 0
Explanation: An empty string has no substrings, so the answer is 0.
Input: ("a", 0)
Expected Output: 0
Explanation: With k = 0, no non-empty substring is allowed.
Input: ("abaccc", 2)
Expected Output: 4
Explanation: The longest valid substring is 'accc', which contains only 'a' and 'c'.
Solution
def solution(s, k):
if k == 0 or not s:
return 0
counts = {}
left = 0
best = 0
for right, ch in enumerate(s):
counts[ch] = counts.get(ch, 0) + 1
while len(counts) > k:
left_ch = s[left]
counts[left_ch] -= 1
if counts[left_ch] == 0:
del counts[left_ch]
left += 1
best = max(best, right - left + 1)
return bestTime complexity: O(len(s)). Space complexity: O(min(len(s), k)).
Hints
- A sliding window can maintain a valid substring while expanding to the right.
- Keep character counts in the current window, and shrink from the left whenever the number of distinct characters exceeds k.