Linked List in Binary Tree
MedPrompt
Given a binary tree root and a linked list with head as the first node.
Return True if all the elements in the linked list starting from the head correspond to some downward path connected in the binary tree otherwise return False.
In this context downward path means a path that starts at some node and goes downwards.
Example 1:

Input: head = [4,2,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
Output: true
Explanation: Nodes in blue form a subpath in the binary Tree. Example 2:

Input: head = [1,4,2,6], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
Output: trueExample 3:
head
Constraints:
- The number of nodes in the tree will be in the range
[1, 2500]. - The number of nodes in the list will be in the range
[1, 100]. 1 <= Node.val <= 100for each node in the linked list and binary tree.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves traversing every node of the binary tree. For each node, we perform a separate check to see if a downward path starting from that node matches the entire linked list. This is straightforward but can be inefficient as it may re-check the same subpaths multiple times.
Algorithm
- Define a main function
isSubPath(head, root). - If
rootis null, returnfalseas no path can be formed. - Call a helper function
dfs(head, root)to check if a path matching the list starts at the currentroot. - If
dfsreturnstrue, we found a match, so returntrue. - Otherwise, recursively call
isSubPathon the left and right children:isSubPath(head, root.left) || isSubPath(head, root.right). - The helper function
dfs(listNode, treeNode)works as follows: - If
listNodeis null, returntrue(end of list reached). - If
treeNodeis null orlistNode.val != treeNode.val, returnfalse(path broken). - Return the result of checking the next list node against the left and right children:
dfs(listNode.next, treeNode.left) || dfs(listNode.next, treeNode.right).
Walkthrough
We define two recursive functions.
The main function, isSubPath(head, root), traverses the entire tree. Its purpose is to find a starting node in the tree that matches the head of the linked list.
- It checks three conditions:
- Does a path starting from the current
rootmatch the list? (This is checked by a helper function). - If not, does a matching path exist in the left subtree? (Recursive call:
isSubPath(head, root.left)). - If not, does a matching path exist in the right subtree? (Recursive call:
isSubPath(head, root.right)).
- Does a path starting from the current
- If any of these are true, we've found a match.
The helper function, dfs(listNode, treeNode), checks if a path starting from treeNode matches the list starting from listNode.
- Base Case 1: If
listNodeis null, it means we've successfully matched all elements of the list. Returntrue. - Base Case 2: If
treeNodeis null or the values don't match (treeNode.val != listNode.val), the path is broken. Returnfalse. - Recursive Step: If the current nodes match, we continue the search downwards. We check if the rest of the list (
listNode.next) matches a path starting from either the left child (treeNode.left) or the right child (treeNode.right). Returndfs(listNode.next, treeNode.left) || dfs(listNode.next, treeNode.right).
The initial call is isSubPath(head, root).
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } *//** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */class Solution { public boolean isSubPath(ListNode head, TreeNode root) { if (root == null) { return false; } // Check if a path starts from the current root if (dfs(head, root)) { return true; } // If not, check the left and right subtrees return isSubPath(head, root.left) || isSubPath(head, root.right); } // Helper function to check for a matching path from a given tree node private boolean dfs(ListNode head, TreeNode root) { // Base case: If we've reached the end of the list, we found a match if (head == null) { return true; } // Base case: If we've run out of tree nodes or values don't match if (root == null || head.val != root.val) { return false; } // Recursive step: Check left and right children for the next list node return dfs(head.next, root.left) || dfs(head.next, root.right); }}Complexity
Time
O(N * L), where N is the number of nodes in the tree and L is the length of the linked list. In the worst case, for each of the N tree nodes, we might traverse L nodes deep to check for a path match.
Space
O(H), where H is the height of the tree. This is due to the recursion stack depth. In the worst-case scenario of a skewed tree, H can be equal to N, making the space complexity O(N).
Trade-offs
Pros
Simple to understand and implement.
Uses a standard recursive pattern for tree problems.
Cons
Inefficient due to repeated computations. The
dfshelper might be called on the same nodes multiple times for different starting points.Can lead to a 'Time Limit Exceeded' error on large test cases.
This approach improves upon the brute-force method by using the Knuth-Morris-Pratt (KMP) string searching algorithm. The KMP algorithm is known for its efficiency in finding a pattern within a text by pre-processing the pattern to avoid redundant comparisons. Here, the linked list is our 'pattern' and the paths in the tree are our 'text'. We traverse the tree only once, using the KMP's state-machine-like logic to track the matching progress.
Algorithm
- Convert the linked list into an integer array
patternof lengthL. - Compute the KMP Longest Proper Prefix Suffix (LPS) array for
pattern. This takes O(L) time. - Define a DFS function
dfs(node, j, pattern, lps)wherejis the current length of the matched prefix. - Inside
dfs, ifnodeis null or a match has already been found, return. - Apply the KMP matching logic: while
j > 0andnode.val != pattern[j], updatejtolps[j-1]. - If
node.val == pattern[j], incrementj. - If
jequals the pattern lengthL, a full match is found. Set a flag totrueand return. - Recursively call
dfsfor the left and right children with the new value ofj. - Initiate the process by calling
dfs(root, 0, pattern, lps).
Walkthrough
The core idea is to avoid restarting the search from scratch when a mismatch occurs. The KMP algorithm uses a precomputed 'Longest Proper Prefix which is also a Suffix' (LPS) array to know where to resume matching.
Step 1: Pre-processing
- Convert the linked list into an integer array, let's call it
pattern. This makes random access possible. - Compute the LPS array for the
pattern. Thelps[i]value stores the length of the longest proper prefix ofpattern[0...i]that is also a suffix ofpattern[0...i]. This step takes O(L) time.
Step 2: KMP-style DFS Traversal
- We perform a single Depth-First Search (DFS) on the tree. The DFS function will take the current
treeNodeand the current length of the matched prefix,j, as parameters:kmpDfs(treeNode, j). - Inside
kmpDfs(treeNode, j):- If a full match has already been found, we can stop early (optional optimization).
- Using the KMP logic, we find the new match length. While
j > 0and the currenttreeNode.valdoes not matchpattern[j], we 'fall back' by settingj = lps[j-1]. This efficiently finds the next best partial match. - If
treeNode.valmatchespattern[j], we extend our match by incrementingj. - If
jbecomes equal to the length of the pattern (L), we have found a complete match. We can set a global flag totrueand return. - Recursively call
kmpDfsfor the left and right children, passing the updated match lengthj.
The initial call is kmpDfs(root, 0).
class Solution { private boolean found = false; public boolean isSubPath(ListNode head, TreeNode root) { if (head == null) return true; if (root == null) return false; // 1. Convert linked list to an array (pattern) java.util.List<Integer> patternList = new java.util.ArrayList<>(); ListNode current = head; while (current != null) { patternList.add(current.val); current = current.next; } int[] pattern = patternList.stream().mapToInt(i -> i).toArray(); // 2. Compute the KMP (LPS) array int[] lps = computeLPS(pattern); // 3. Perform DFS with KMP logic dfs(root, 0, pattern, lps); return found; } private void dfs(TreeNode node, int j, int[] pattern, int[] lps) { if (node == null || found) { return; } // KMP match logic while (j > 0 && node.val != pattern[j]) { j = lps[j - 1]; } if (node.val == pattern[j]) { j++; } // Check if pattern is fully matched if (j == pattern.length) { found = true; return; } // Recurse on children dfs(node.left, j, pattern, lps); dfs(node.right, j, pattern, lps); } private int[] computeLPS(int[] pattern) { int m = pattern.length; int[] lps = new int[m]; int length = 0; // length of the previous longest prefix suffix int i = 1; while (i < m) { if (pattern[i] == pattern[length]) { length++; lps[i] = length; i++; } else { if (length != 0) { length = lps[length - 1]; } else { lps[i] = 0; i++; } } } return lps; }}Complexity
Time
O(N + L), where N is the number of nodes in the tree and L is the length of the list. Converting the list to an array and building the LPS array takes O(L). The DFS traversal visits each tree node once. The KMP logic at each node has an amortized constant time complexity, leading to an O(N) traversal. Thus, the total time is O(N + L).
Space
O(L + H), where L is the list length and H is the tree height. We need O(L) space for the pattern array and the LPS array. The recursion stack for DFS requires O(H) space. In the worst case, H can be N, making the space O(L + N).
Trade-offs
Pros
Highly efficient with linear time complexity.
Avoids redundant comparisons by intelligently reusing information from previous partial matches.
Cons
More complex to understand and implement compared to the brute-force approach.
Requires knowledge of the KMP algorithm.
Solutions
Solution
/** * Definition for singly-linked list. * public class ListNode { * int val; * ListNode next; * ListNode() {} * ListNode(int val) { this.val = val; } * ListNode(int val, ListNode next) { this.val = val; this.next = next; } * } */ /** * Definition for a binary tree node. * public class TreeNode { * int val; * TreeNode left; * TreeNode right; * TreeNode() {} * TreeNode(int val) { this.val = val; } * TreeNode(int val, TreeNode left, TreeNode right) { * this.val = val; * this.left = left; * this.right = right; * } * } */ class Solution { public boolean isSubPath ( ListNode head , TreeNode root ) { if ( root == null ) { return false ; } return dfs ( head , root ) || isSubPath ( head , root . left ) || isSubPath ( head , root . right ); } private boolean dfs ( ListNode head , TreeNode root ) { if ( head == null ) { return true ; } if ( root == null || head . val != root . val ) { return false ; } return dfs ( head . next , root . left ) || dfs ( head . next , root . right ); } }/** * Definition for singly-linked list. * struct ListNode { * int val; * ListNode *next; * ListNode() : val(0), next(nullptr) {} * ListNode(int x) : val(x), next(nullptr) {} * ListNode(int x, ListNode *next) : val(x), next(next) {} * }; */ /** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode() : val(0), left(nullptr), right(nullptr) {} * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {} * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} * }; */ class Solution { public: bool isSubPath ( ListNode * head , TreeNode * root ) { if ( ! root ) { return false ; } return dfs ( head , root ) || isSubPath ( head , root -> left ) || isSubPath ( head , root -> right ); } bool dfs ( ListNode * head , TreeNode * root ) { if ( ! head ) { return true ; } if ( ! root || head -> val != root -> val ) { return false ; } return dfs ( head -> next , root -> left ) || dfs ( head -> next , root -> right ); } };# 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 isSubPath ( self , head : Optional [ ListNode ], root : Optional [ TreeNode ]) -> bool : def dfs ( head , root ): if head is None : return True if root is None or root . val != head . val : return False return dfs ( head . next , root . left ) or dfs ( head . next , root . right ) if root is None : return False return ( dfs ( head , root ) or self . isSubPath ( head , root . left ) or self . isSubPath ( head , root . right ) )Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.