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 dfs that 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.
Collect leaves from both trees into separate lists.
Compare the two lists and return whether they are equal.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolLeafSimilar(TreeNode root1,TreeNode root2){List<int> leaf1 =newList<int>();List<int> leaf2 =newList<int>();Dfs(root1, leaf1);Dfs(root2, leaf2);return leaf1.SequenceEqual(leaf2);}privatevoidDfs(TreeNode root,List<int> leaf){if(root ==null)return;if(root.left ==null&& root.right ==null){
leaf.Add(root.val);return;}Dfs(root.left, leaf);Dfs(root.right, leaf);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcleafSimilar(root1 *TreeNode, root2 *TreeNode)bool{var dfs func(root *TreeNode, leaf *[]int)
dfs =func(root *TreeNode, leaf *[]int){if root ==nil{return}if root.Left ==nil&& root.Right ==nil{*leaf =append(*leaf, root.Val)return}dfs(root.Left, leaf)dfs(root.Right, leaf)}
leaf1, leaf2 :=[]int{},[]int{}dfs(root1,&leaf1)dfs(root2,&leaf2)iflen(leaf1)!=len(leaf2){returnfalse}for i :=range leaf1 {if leaf1[i]!= leaf2[i]{returnfalse}}returntrue}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funleafSimilar(root1: TreeNode?, root2: TreeNode?): Boolean {val leaf1 = mutableListOf<Int>()val leaf2 = mutableListOf<Int>()dfs(root1, leaf1)dfs(root2, leaf2)return leaf1 == leaf2
}privatefundfs(root: TreeNode?, leaf: MutableList<Int>){if(root ==null)returnif(root.left ==null&& root.right ==null){
leaf.add(root.`val`)return}dfs(root.left, leaf)dfs(root.right, leaf)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcleafSimilar(_ root1:TreeNode?,_ root2:TreeNode?)->Bool{var leaf1 =[Int]()var leaf2 =[Int]()dfs(root1,&leaf1)dfs(root2,&leaf2)return leaf1 == leaf2
}privatefuncdfs(_ root:TreeNode?,_ leaf:inout[Int]){guardlet root = root else{return}if root.left==nil&& root.right==nil{
leaf.append(root.val)return}dfs(root.left,&leaf)dfs(root.right,&leaf)}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(n+m)
Where n and m 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 false on mismatch or if the stack is empty.
After traversing the second tree, check that the stack is empty (no extra leaves in the first tree).
Return true only if all leaves matched and both trees had the same number of leaves.
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolLeafSimilar(TreeNode root1,TreeNode root2){Stack<int> leaf1 =newStack<int>();Dfs(root1, leaf1);returnDfs1(root2, leaf1)&& leaf1.Count ==0;}privatevoidDfs(TreeNode root,Stack<int> leaf){if(root ==null)return;if(root.left ==null&& root.right ==null){
leaf.Push(root.val);return;}Dfs(root.left, leaf);Dfs(root.right, leaf);}privateboolDfs1(TreeNode root,Stack<int> leaf){if(root ==null)returntrue;if(root.left ==null&& root.right ==null){if(leaf.Count ==0)returnfalse;return leaf.Pop()== root.val;}returnDfs1(root.right, leaf)&&Dfs1(root.left, leaf);}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcleafSimilar(root1 *TreeNode, root2 *TreeNode)bool{var dfs func(root *TreeNode, leaf *[]int)
dfs =func(root *TreeNode, leaf *[]int){if root ==nil{return}if root.Left ==nil&& root.Right ==nil{*leaf =append(*leaf, root.Val)return}dfs(root.Left, leaf)dfs(root.Right, leaf)}var dfs1 func(root *TreeNode, leaf *[]int)bool
dfs1 =func(root *TreeNode, leaf *[]int)bool{if root ==nil{returntrue}if root.Left ==nil&& root.Right ==nil{iflen(*leaf)==0{returnfalse}
val :=(*leaf)[len(*leaf)-1]*leaf =(*leaf)[:len(*leaf)-1]return val == root.Val
}returndfs1(root.Right, leaf)&&dfs1(root.Left, leaf)}
leaf1 :=[]int{}dfs(root1,&leaf1)returndfs1(root2,&leaf1)&&len(leaf1)==0}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funleafSimilar(root1: TreeNode?, root2: TreeNode?): Boolean {val leaf1 = ArrayDeque<Int>()dfs(root1, leaf1)returndfs1(root2, leaf1)&& leaf1.isEmpty()}privatefundfs(root: TreeNode?, leaf: ArrayDeque<Int>){if(root ==null)returnif(root.left ==null&& root.right ==null){
leaf.addLast(root.`val`)return}dfs(root.left, leaf)dfs(root.right, leaf)}privatefundfs1(root: TreeNode?, leaf: ArrayDeque<Int>): Boolean {if(root ==null)returntrueif(root.left ==null&& root.right ==null){if(leaf.isEmpty())returnfalsereturn leaf.removeLast()== root.`val`
}returndfs1(root.right, leaf)&&dfs1(root.left, leaf)}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcleafSimilar(_ root1:TreeNode?,_ root2:TreeNode?)->Bool{var leaf1 =[Int]()dfs(root1,&leaf1)returndfs1(root2,&leaf1)&& leaf1.isEmpty
}privatefuncdfs(_ root:TreeNode?,_ leaf:inout[Int]){guardlet root = root else{return}if root.left==nil&& root.right==nil{
leaf.append(root.val)return}dfs(root.left,&leaf)dfs(root.right,&leaf)}privatefuncdfs1(_ root:TreeNode?,_ leaf:inout[Int])->Bool{guardlet root = root else{returntrue}if root.left==nil&& root.right==nil{if leaf.isEmpty {returnfalse}return leaf.popLast()== root.val
}returndfs1(root.right,&leaf)&&dfs1(root.left,&leaf)}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(n+m)
Where n and m 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 getPathLeaf that 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 true only if both stacks are empty (both trees exhausted their leaves together).
/**
* Definition for a binary tree node.
* public class TreeNode {
* public int val;
* public TreeNode left;
* public TreeNode right;
* public TreeNode(int val=0, TreeNode left=null, TreeNode right=null) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/publicclassSolution{publicboolLeafSimilar(TreeNode root1,TreeNode root2){Stack<TreeNode> stack1 =newStack<TreeNode>();Stack<TreeNode> stack2 =newStack<TreeNode>();
stack1.Push(root1);
stack2.Push(root2);while(stack1.Count >0&& stack2.Count >0){if(GetPathLeaf(stack1)!=GetPathLeaf(stack2)){returnfalse;}}return stack1.Count ==0&& stack2.Count ==0;}privateintGetPathLeaf(Stack<TreeNode> stack){while(stack.Count >0){TreeNode node = stack.Pop();if(node.right !=null) stack.Push(node.right);if(node.left !=null) stack.Push(node.left);if(node.left ==null&& node.right ==null){return node.val;}}return-1;}}
/**
* Definition for a binary tree node.
* type TreeNode struct {
* Val int
* Left *TreeNode
* Right *TreeNode
* }
*/funcleafSimilar(root1 *TreeNode, root2 *TreeNode)bool{
getPathLeaf :=func(stack *[]*TreeNode)int{forlen(*stack)>0{
node :=(*stack)[len(*stack)-1]*stack =(*stack)[:len(*stack)-1]if node.Right !=nil{*stack =append(*stack, node.Right)}if node.Left !=nil{*stack =append(*stack, node.Left)}if node.Left ==nil&& node.Right ==nil{return node.Val
}}return-1}
stack1, stack2 :=[]*TreeNode{root1},[]*TreeNode{root2}forlen(stack1)>0&&len(stack2)>0{ifgetPathLeaf(&stack1)!=getPathLeaf(&stack2){returnfalse}}returnlen(stack1)==0&&len(stack2)==0}
/**
* Example:
* var ti = TreeNode(5)
* var v = ti.`val`
* Definition for a binary tree node.
* class TreeNode(var `val`: Int) {
* var left: TreeNode? = null
* var right: TreeNode? = null
* }
*/class Solution {funleafSimilar(root1: TreeNode?, root2: TreeNode?): Boolean {val stack1 = ArrayDeque<TreeNode>()val stack2 = ArrayDeque<TreeNode>()
root1?.let{ stack1.addLast(it)}
root2?.let{ stack2.addLast(it)}while(stack1.isNotEmpty()&& stack2.isNotEmpty()){if(getPathLeaf(stack1)!=getPathLeaf(stack2)){returnfalse}}return stack1.isEmpty()&& stack2.isEmpty()}privatefungetPathLeaf(stack: ArrayDeque<TreeNode>): Int {while(stack.isNotEmpty()){val node = stack.removeLast()
node.right?.let{ stack.addLast(it)}
node.left?.let{ stack.addLast(it)}if(node.left ==null&& node.right ==null){return node.`val`
}}return-1}}
/**
* Definition for a binary tree node.
* public class TreeNode {
* public var val: Int
* public var left: TreeNode?
* public var right: TreeNode?
* public init() { self.val = 0; self.left = nil; self.right = nil; }
* public init(_ val: Int) { self.val = val; self.left = nil; self.right = nil; }
* public init(_ val: Int, _ left: TreeNode?, _ right: TreeNode?) {
* self.val = val
* self.left = left
* self.right = right
* }
* }
*/classSolution{funcleafSimilar(_ root1:TreeNode?,_ root2:TreeNode?)->Bool{var stack1 =[TreeNode]()var stack2 =[TreeNode]()iflet r1 = root1 { stack1.append(r1)}iflet r2 = root2 { stack2.append(r2)}while!stack1.isEmpty &&!stack2.isEmpty {ifgetPathLeaf(&stack1)!=getPathLeaf(&stack2){returnfalse}}return stack1.isEmpty && stack2.isEmpty
}privatefuncgetPathLeaf(_ stack:inout[TreeNode])->Int{while!stack.isEmpty {let node = stack.removeLast()ifletright= node.right{ stack.append(right)}ifletleft= node.left{ stack.append(left)}if node.left==nil&& node.right==nil{return node.val
}}return-1}}
Time & Space Complexity
Time complexity: O(n+m)
Space complexity: O(n+m)
Where n and m 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.