Check inorder subsequence and edit tree minimally
Company: Airbnb
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: Medium
Interview Round: Technical Screen
Quick Answer: This question evaluates proficiency in tree traversal and subsequence matching under tight resource constraints, testing skills in streaming inorder traversal, memory-efficient algorithms, and reasoning about subsequences within binary trees; it falls under the Coding & Algorithms domain and requires practical implementation ability combined with conceptual algorithmic reasoning. The follow-up on minimally editing the tree and computing a minimum-cost edit sequence assesses understanding of edit-distance-like reasoning on tree structures, handling edge cases (empty target, duplicates, longer target) and analyzing time/space complexity, which is commonly asked to evaluate optimization, problem decomposition, and correctness under constraints.
Part 1: Check Whether Target Is an Inorder Subsequence
Constraints
- 0 <= number of nodes <= 100000
- 0 <= len(target) <= 100000
- -10^9 <= node values, target values <= 10^9
- Duplicate values are allowed
Examples
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [1, 3])
Expected Output: True
Explanation: The inorder traversal is [1, 2, 3], and [1, 3] is a subsequence.
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [3, 1])
Expected Output: False
Explanation: The values appear in the wrong relative order.
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 2, 'left': None, 'right': None}}, [2, 2])
Expected Output: True
Explanation: The inorder traversal is [1, 2, 2], so the duplicate target is present as a subsequence.
Input: (None, [])
Expected Output: True
Explanation: The empty array is a subsequence of any traversal, including an empty one.
Input: (None, [1])
Expected Output: False
Explanation: A non-empty target cannot be a subsequence of an empty traversal.
Solution
def solution(tree, target):
if not target:
return True
i = 0
current = tree
while current is not None:
if current['left'] is None:
if i < len(target) and current['val'] == target[i]:
i += 1
current = current['right']
else:
pred = current['left']
while pred['right'] is not None and pred['right'] is not current:
pred = pred['right']
if pred['right'] is None:
pred['right'] = current
current = current['left']
else:
pred['right'] = None
if i < len(target) and current['val'] == target[i]:
i += 1
current = current['right']
return i == len(target)
Time complexity: O(n), where n is the number of nodes in the tree.. Space complexity: O(1) auxiliary space..
Hints
- Keep one pointer into target. Advance it only when the current inorder value matches target[pointer].
- Morris inorder traversal lets you stream inorder values using O(1) auxiliary space by temporarily threading the tree.
Part 2: Deterministically Modify the Tree So Target Appears in Inorder
Constraints
- 0 <= number of nodes <= 100000
- 0 <= len(target) <= 100000
- -10^9 <= node values, target values <= 10^9
- Duplicate values are allowed
Examples
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [4, 5])
Expected Output: {'val': 5, 'left': {'val': 4, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}
Explanation: The first two inorder nodes are overwritten with 4 and 5. The last node stays unchanged.
Input: ({'val': 7, 'left': None, 'right': None}, [1, 2, 3])
Expected Output: {'val': 1, 'left': None, 'right': {'val': 2, 'left': None, 'right': {'val': 3, 'left': None, 'right': None}}}
Explanation: Overwrite the only existing node with 1, then append 2 and 3 as a right chain.
Input: ({'val': 1, 'left': None, 'right': None}, [])
Expected Output: {'val': 1, 'left': None, 'right': None}
Explanation: An empty target requires no change.
Input: (None, [9, 8])
Expected Output: {'val': 9, 'left': None, 'right': {'val': 8, 'left': None, 'right': None}}
Explanation: An empty tree becomes a pure right-child chain containing the target values.
Solution
def solution(tree, target):
def new_node(value):
return {'val': value, 'left': None, 'right': None}
if not target:
return tree
if tree is None:
root = new_node(target[0])
cur = root
for value in target[1:]:
cur['right'] = new_node(value)
cur = cur['right']
return root
i = 0
current = tree
last = None
while current is not None:
if current['left'] is None:
if i < len(target):
current['val'] = target[i]
i += 1
last = current
current = current['right']
else:
pred = current['left']
while pred['right'] is not None and pred['right'] is not current:
pred = pred['right']
if pred['right'] is None:
pred['right'] = current
current = current['left']
else:
pred['right'] = None
if i < len(target):
current['val'] = target[i]
i += 1
last = current
current = current['right']
tail = last
while i < len(target):
tail['right'] = new_node(target[i])
tail = tail['right']
i += 1
return tree
Time complexity: O(n + m), where n is the number of original nodes and m is len(target).. Space complexity: O(1) auxiliary space, excluding the newly inserted output nodes..
Hints
- If you overwrite the first visited inorder nodes with target values in order, target becomes a prefix of the modified inorder traversal.
- Any remaining target values can be appended at the end of inorder by attaching a right-child chain to the original rightmost node.
Part 3: Minimum-Cost Edit Plan for Inorder Subsequence
Constraints
- 0 <= number of nodes <= 200
- 0 <= len(target) <= 200
- -10^9 <= node values, target values <= 10^9
- Duplicate values are allowed
Examples
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [1, 3])
Expected Output: (0, [])
Explanation: The target is already a subsequence of the inorder traversal [1, 2, 3].
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [1, 4, 3])
Expected Output: (1, [('overwrite', 1, 4)])
Explanation: Using the deterministic tie-break, overwrite original inorder index 1 (value 2) to 4.
Input: ({'val': 2, 'left': {'val': 1, 'left': None, 'right': None}, 'right': {'val': 3, 'left': None, 'right': None}}, [3, 1])
Expected Output: (1, [('insert_before', 0, 3)])
Explanation: Insert 3 before original inorder index 0, then use the original 1.
Input: ({'val': 5, 'left': None, 'right': None}, [1])
Expected Output: (1, [('overwrite', 0, 1)])
Explanation: Overwrite the only original node.
Input: (None, [2, 2])
Expected Output: (2, [('insert_before', 0, 2), ('insert_before', 0, 2)])
Explanation: With no original nodes, both target values must be inserted.
Solution
def solution(tree, target):
inorder = []
stack = []
current = tree
while stack or current is not None:
while current is not None:
stack.append(current)
current = current['left']
current = stack.pop()
inorder.append(current['val'])
current = current['right']
n = len(inorder)
m = len(target)
dp = [[0] * (m + 1) for _ in range(n + 1)]
for j in range(m - 1, -1, -1):
dp[n][j] = m - j
for i in range(n - 1, -1, -1):
for j in range(m - 1, -1, -1):
skip_cost = dp[i + 1][j]
use_cost = (0 if inorder[i] == target[j] else 1) + dp[i + 1][j + 1]
insert_cost = 1 + dp[i][j + 1]
dp[i][j] = min(skip_cost, use_cost, insert_cost)
edits = []
i = 0
j = 0
while j < m:
if i < n:
use_cost = (0 if inorder[i] == target[j] else 1) + dp[i + 1][j + 1]
if use_cost == dp[i][j]:
if inorder[i] != target[j]:
edits.append(('overwrite', i, target[j]))
i += 1
j += 1
continue
insert_cost = 1 + dp[i][j + 1]
if insert_cost == dp[i][j]:
edits.append(('insert_before', i, target[j]))
j += 1
continue
i += 1
else:
edits.append(('insert_before', n, target[j]))
j += 1
return dp[0][0], edits
Time complexity: O(n * m), where n is the inorder length and m is len(target).. Space complexity: O(n * m)..
Hints
- After converting the tree to its inorder sequence, the problem becomes sequence DP.
- At DP state (i, j), consider three choices: skip inorder[i], use inorder[i] for target[j] with cost 0 or 1, or insert target[j] with cost 1.