Problem of The Day: Linked List in Binary Tree
Problem Statement
Intuition
The goal is to check if a linked list is a subpath of a binary tree. A BFS approach would help us explore all possible paths starting from a match between the head of the linked list and the tree nodes. If a match is found, we traverse deeper to check if the subsequent nodes match as well.
Approach
We traverse the binary tree using a BFS (breadth-first search) approach, starting from each node in the tree that matches the value of the head of the linked list. For each matching node, we attempt to follow the path of the linked list in the tree. If we successfully match all values of the linked list in the tree, we return True
. If no matching path is found, we return False
.
Steps:
- Perform BFS from each tree node that matches the head of the linked list.
- At each tree node, attempt to match the next node of the linked list recursively in the left and right subtrees.
- If the linked list is fully matched, return
True
. - If the traversal ends without finding a subpath, return
False
.
Complexity
-
Time complexity: \(O(n \times m)\) where (n) is the number of nodes in the binary tree and (m) is the length of the linked list.
-
Space complexity: The space complexity is \(O(n)\) due to the queue in BFS, where (n) is the number of nodes in the tree.
Code
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def bfs(self, head, root):
queue = deque([(root, head)])
while queue:
node, head = queue.popleft()
if not head: break
if node and node.val == head.val:
queue.append((node.left, head.next))
queue.append((node.right, head.next))
return head is None
def isSubPath(self, head: Optional[ListNode], root: Optional[TreeNode]) -> bool:
if not root:
return False
if head.val == root.val:
if self.bfs(head, root):
return True
return self.isSubPath(head, root.left) or self.isSubPath(head, root.right)
Editorial
Approach 1: DFS
class Solution:
def isSubPath(
self, head: Optional[ListNode], root: Optional[TreeNode]
) -> bool:
if root is None:
return False
return self._check_path(root, head)
def _check_path(
self, node: Optional[TreeNode], head: Optional[ListNode]
) -> bool:
if node is None:
return False
if self._dfs(node, head):
return True # If a matching path is found
# Recursively check left and right subtrees
return self._check_path(node.left, head) or self._check_path(
node.right, head
)
def _dfs(self, node: Optional[TreeNode], head: Optional[ListNode]) -> bool:
if head is None:
return True # All nodes in the list matched
if node is None:
return False # Reached end of tree without matching all nodes
if node.val != head.val:
return False # Value mismatch
return self._dfs(node.left, head.next) or self._dfs(
node.right, head.next
)
- time: O(n * m)
- space: O(n + m)
Approach 2: Iterative Approach
class Solution:
def isSubPath(
self, head: Optional[ListNode], root: Optional[TreeNode]
) -> bool:
if not root:
return False
stack = [root]
while stack:
node = stack.pop()
if self._is_match(node, head):
return True
# Push left and right children onto the stack
if node.left:
stack.append(node.left)
if node.right:
stack.append(node.right)
return False
def _is_match(
self, node: Optional[TreeNode], lst: Optional[ListNode]
) -> bool:
# Stack to keep track of (current_tree_node, current_list_node)
stack = [(node, lst)]
while stack:
current_node, current_list = stack.pop()
while current_node and current_list:
if current_node.val != current_list.val:
break
current_list = current_list.next
# Continue to the next node in the tree, left or right
if current_list:
if current_node.left:
stack.append((current_node.left, current_list))
if current_node.right:
stack.append((current_node.right, current_list))
break
if not current_list:
return True
return False
- time: O(n * m)
- space: O(n)