Before attempting this problem, you should be comfortable with:
We need to find the lexicographically smallest string that starts at a leaf and ends at the root. Since strings are built from leaf to root, we traverse the tree while building the string by prepending each node's character. When we reach a leaf, we have a complete string to compare. By exploring all paths and keeping track of the minimum string found, we get our answer.
dfs from the root, passing the current string built so far.dfs on that child.# 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 smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
def dfs(root, cur):
if not root:
return
cur = chr(ord('a') + root.val) + cur
if root.left and root.right:
return min(
dfs(root.left, cur),
dfs(root.right, cur)
)
if root.right:
return dfs(root.right, cur)
if root.left:
return dfs(root.left, cur)
return cur
return dfs(root, "")Instead of recursion, we can use a queue to traverse the tree level by level. Each queue entry stores a node along with the string built from the root down to that node. When we encounter a leaf, we compare its complete string with the current minimum. BFS naturally explores all paths, and we simply track the smallest leaf-to-root string found.
# 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 smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
q = deque([(root, "")])
res = None
while q:
node, cur = q.popleft()
cur = chr(ord('a') + node.val) + cur
if not node.left and not node.right:
res = min(res, cur) if res else cur
if node.left:
q.append((node.left, cur))
if node.right:
q.append((node.right, cur))
return resThis approach mirrors the recursive DFS but uses an explicit stack instead of the call stack. We push nodes along with their accumulated strings onto the stack. By processing nodes in LIFO order, we achieve depth-first traversal. The logic for building strings and comparing at leaves remains the same as the recursive version.
# 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 smallestFromLeaf(self, root: Optional[TreeNode]) -> str:
stack = [(root, "")]
res = None
while stack:
node, cur = stack.pop()
cur = chr(ord('a') + node.val) + cur
if not node.left and not node.right:
res = min(res, cur) if res else cur
if node.right:
stack.append((node.right, cur))
if node.left:
stack.append((node.left, cur))
return resThe string must be built from leaf to root, not root to leaf. This means prepending each character as you traverse down the tree. A common mistake is appending characters during traversal, which produces the reversed string. Always use cur = char + cur (prepend), not cur = cur + char (append).
Only complete strings from leaf nodes should be compared. If you compare strings at internal nodes, you might select a prefix that leads to a suboptimal complete string. Always ensure comparisons only happen when node.left == null && node.right == null, confirming the node is truly a leaf.
When a node has only one child, you must continue traversal on that child. A common bug is treating nodes with one child as leaves or skipping the single child entirely. This causes valid paths to be missed. Always check for left-only and right-only cases separately from the two-children and no-children cases.