872. Leaf-Similar Trees - Explanation
Description
A leaf is a node with no children. Read the leaf values of each tree from left to right. Return true if the two resulting sequences are identical, including their order and repeated values; otherwise return false. The shapes and internal values of the trees may differ.
Each tree is encoded as a level-order array. After the root, each present node contributes left and right child entries in queue order. null marks a missing child, and trailing missing entries may be omitted.
Example 1:
Input: root1 = [8,2,5]
root2 = [9,4,5,2]
Output: trueExplanation: Both trees have leaf values [2,5] from left to right. Their internal nodes and shapes differ, but the leaf sequences match.
Example 2:
Input: root1 = [8,2,5]
root2 = [9,5,2]
Output: falseExplanation: The first leaf sequence is [2,5], while the second is [5,2]. The same values in a different order do not match.
Example 3:
Input: root1 = [4]
root2 = [7,4]
Output: trueExplanation: The single node in the first tree is a leaf with value 4. In the second tree, the root has a left child, so only that child contributes to the leaf sequence [4].
Constraints:
- Each tree contains between 1 and 200 nodes.
0 <= Node.val <= 200
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Trees - Understanding tree structure, node representation, and the concept of leaf nodes (nodes with no children)
- Depth-First Search (DFS) - Recursive traversal of trees and understanding pre-order traversal to visit nodes in left-to-right order
- Recursion - Writing recursive functions with base cases and recursive calls
- Stacks - For iterative DFS implementations, understanding LIFO data structure for managing traversal state
1. Depth First Search
Intuition
Two trees are leaf-similar if their leaf nodes, read from left to right, form the same sequence. We can collect the leaf values from each tree using a depth-first traversal. A node is a leaf if it has no children. By traversing left before right, we naturally encounter leaves in left-to-right order.
Algorithm
- Create a helper function
dfsthat traverses a tree and appends leaf values to a list. - For each node:
- If it's
null, return immediately. - If it's a leaf (no left or right child), add its value to the list.
- Otherwise, recursively process the left subtree, then the right subtree.
- If it's
- Collect leaves from both trees into separate lists.
- Compare the two lists and return whether they are equal.
# 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 leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool:
def dfs(root, leaf):
if not root:
return
if not root.left and not root.right:
leaf.append(root.val)
return
dfs(root.left, leaf)
dfs(root.right, leaf)
leaf1, leaf2 = [], []
dfs(root1, leaf1)
dfs(root2, leaf2)
return leaf1 == leaf2Time & Space Complexity
- Time complexity:
- Space complexity:
Where and are the number of nodes in the given trees.
2. Depth First Search (Space Optimized)
Intuition
Instead of storing both leaf sequences and comparing at the end, we can collect leaves from the first tree and then verify them against the second tree on the fly. By traversing the second tree in reverse order (right to left) and using a stack, we can pop leaves one by one and compare them immediately. This saves space when one tree has significantly fewer leaves than expected.
Algorithm
- Traverse the first tree using DFS and store all leaf values in a stack (leaves appear in reverse order due to left-to-right traversal).
- Define a second DFS function for the second tree that traverses right-to-left:
- If a leaf is found, pop from the stack and compare. Return
falseon mismatch or if the stack is empty.
- If a leaf is found, pop from the stack and compare. Return
- After traversing the second tree, check that the stack is empty (no extra leaves in the first tree).
- Return
trueonly if all leaves matched and both trees had the same number of leaves.
# 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 leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool:
def dfs(root, leaf):
if not root:
return
if not root.left and not root.right:
leaf.append(root.val)
return
dfs(root.left, leaf)
dfs(root.right, leaf)
leaf1 = []
dfs(root1, leaf1)
def dfs1(root, leaf):
if not root:
return True
if not root.left and not root.right:
if not leaf:
return False
return leaf.pop() == root.val
return dfs1(root.right, leaf) and dfs1(root.left, leaf)
return dfs1(root2, leaf1) and not leaf1Time & Space Complexity
- Time complexity:
- Space complexity:
Where and are the number of nodes in the given trees.
3. Iterative DFS
Intuition
Rather than collecting all leaves first, we can compare leaves one at a time using two parallel iterative traversals. Each tree maintains its own stack. We advance each stack until we find the next leaf, compare the two leaves, and continue. This approach can exit early if a mismatch is found without traversing the entire trees.
Algorithm
- Initialize two stacks, one for each tree, and push their root nodes.
- Create a helper function
getPathLeafthat pops nodes from a stack until a leaf is found, pushing children along the way. - While both stacks are non-empty:
- Get the next leaf from each stack.
- If the values differ, return
false.
- After the loop, return
trueonly if both stacks are empty (both trees exhausted their leaves together).
# 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 leafSimilar(self, root1: Optional[TreeNode], root2: Optional[TreeNode]) -> bool:
def getPathLeaf(stack):
while stack:
node = stack.pop()
if node.right:
stack.append(node.right)
if node.left:
stack.append(node.left)
if not node.left and not node.right:
return node.val
stack1, stack2 = [root1], [root2]
while stack1 and stack2:
if getPathLeaf(stack1) != getPathLeaf(stack2):
return False
return not stack1 and not stack2Time & Space Complexity
- Time complexity:
- Space complexity:
Where and are the number of nodes in the given trees.
Common Pitfalls
Collecting All Nodes Instead of Just Leaves
A common mistake is to collect all node values during traversal instead of only the leaf nodes. Remember that a leaf is specifically a node with no left and no right child. Including internal nodes in your comparison will produce incorrect results.
Traversing in Wrong Order (Right Before Left)
The problem requires leaves to match in left-to-right order. If you traverse right subtrees before left subtrees, you will collect leaves in the wrong sequence. Always process the left child before the right child in your DFS traversal.
Sign in to join the discussion