Delete nodes in linked list and binary tree
Company: TikTok
Role: Software Engineer
Category: Coding & Algorithms
Difficulty: medium
Interview Round: Technical Screen
Quick Answer: This question evaluates proficiency in mutable data structure operations, specifically linked list node removal and binary tree node replacement/deletion, requiring knowledge of traversal, pointer/reference manipulation, and edge-case handling.
Part 1: Remove the Nth Node from the End of an Encoded Linked List
Constraints
- Let `L` be the number of nodes in the list.
- `1 <= L <= 10^5`
- `1 <= n <= L`
- Node values are integers in the range `[-10^9, 10^9]`
Examples
Input: ((1, (2, (3, (4, (5, None))))), 2)
Expected Output: (1, (2, (3, (5, None))))
Explanation: The 2nd node from the end is `4`, so the result is `1 -> 2 -> 3 -> 5`.
Input: ((1, None), 1)
Expected Output: None
Explanation: Single-node list: deleting the 1st node from the end removes the only node.
Input: ((1, (2, None)), 2)
Expected Output: (2, None)
Explanation: The node to remove is the head because `n` equals the list length.
Input: ((10, (20, (30, None))), 1)
Expected Output: (10, (20, None))
Explanation: Deleting the 1st node from the end removes the tail node `30`.
Solution
def solution(head, n):
class ListNode:
__slots__ = ("val", "next")
def __init__(self, val=0, next=None):
self.val = val
self.next = next
def build(encoded):
dummy = ListNode()
cur = dummy
while encoded is not None:
val, encoded = encoded
cur.next = ListNode(val)
cur = cur.next
return dummy.next
def encode(node):
values = []
while node is not None:
values.append(node.val)
node = node.next
encoded = None
for value in reversed(values):
encoded = (value, encoded)
return encoded
if head is None:
return None
linked_head = build(head)
dummy = ListNode(0, linked_head)
fast = dummy
slow = dummy
for _ in range(n):
fast = fast.next
while fast.next is not None:
fast = fast.next
slow = slow.next
slow.next = slow.next.next
return encode(dummy.next)
Time complexity: O(L). Space complexity: O(L) due to converting between the tuple encoding and linked-list nodes. The core two-pointer deletion step itself uses O(1) extra space..
Hints
- A dummy node before the head makes deleting the first real node much easier.
- Use two pointers: move one pointer `n` steps ahead, then move both pointers together until the front pointer reaches the end.
Part 2: Delete a Node from a Binary Tree Using Deepest-Rightmost Replacement
Constraints
- `0 <= N <= 10^4`, where `N` is the number of non-`None` nodes in the tree
- Tree values are integers in the range `[-10^9, 10^9]`
- The tree is not necessarily a BST
- An `O(N)` time solution is expected
- `O(N)` auxiliary space is allowed
Examples
Input: ([1, 2, 3, 4, 5, 6], 3)
Expected Output: [1, 2, 6, 4, 5]
Explanation: Node `3` is replaced by deepest-rightmost node `6`, and the original `6` is removed.
Input: ([1], 1)
Expected Output: []
Explanation: The tree has one node and it matches `key`, so the result is an empty tree.
Input: ([1, 2, 3], 9)
Expected Output: [1, 2, 3]
Explanation: The key does not exist, so the original tree is returned unchanged.
Input: ([10, 11, 12, None, 13], 11)
Expected Output: [10, 13, 12]
Explanation: The deepest-rightmost node is `13`, which replaces `11`, and the original `13` node is removed.
Input: ([], 5)
Expected Output: []
Explanation: Deleting from an empty tree should still return an empty tree.
Solution
def solution(root, key):
from collections import deque
class TreeNode:
__slots__ = ("val", "left", "right")
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def build(levels):
if not levels or levels[0] is None:
return None
root_node = TreeNode(levels[0])
queue = deque([root_node])
i = 1
while queue and i < len(levels):
node = queue.popleft()
if i < len(levels):
left_val = levels[i]
i += 1
if left_val is not None:
node.left = TreeNode(left_val)
queue.append(node.left)
if i < len(levels):
right_val = levels[i]
i += 1
if right_val is not None:
node.right = TreeNode(right_val)
queue.append(node.right)
return root_node
def serialize(root_node):
if root_node is None:
return []
result = []
queue = deque([root_node])
while queue:
node = queue.popleft()
if node is None:
result.append(None)
else:
result.append(node.val)
queue.append(node.left)
queue.append(node.right)
while result and result[-1] is None:
result.pop()
return result
root_node = build(root)
if root_node is None:
return []
queue = deque([(root_node, None)])
target = None
deepest = None
deepest_parent = None
while queue:
node, parent = queue.popleft()
if target is None and node.val == key:
target = node
deepest = node
deepest_parent = parent
if node.left is not None:
queue.append((node.left, node))
if node.right is not None:
queue.append((node.right, node))
if target is None:
return serialize(root_node)
if deepest_parent is None and target is root_node:
return []
target.val = deepest.val
if deepest_parent.left is deepest:
deepest_parent.left = None
else:
deepest_parent.right = None
return serialize(root_node)
Time complexity: O(N). Space complexity: O(N).
Hints
- A breadth-first search can simultaneously find the target node, the deepest-rightmost node, and each node's parent.
- After replacing the target value, be careful to delete the original deepest-rightmost node from its parent, not the target node itself.