513. Find Bottom Left Tree Value - Explanation
Description
Find the deepest level of the binary tree and return the value of the leftmost node on that level. A node's depth is the number of edges from the root, so the root has depth 0. The answer depends on position, not on which value is numerically smallest.
Examples use level-order arrays. 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: root = [8,6,2,9,null,1,0]
Output: 9Explanation: The deepest row contains [9,1,0]. Its leftmost value is 9, even though the other values are smaller.
Example 2:
Input: root = [4,null,7,3,5]
Output: 3Explanation: The deepest row contains [3,5], both in the root's right subtree. The leftmost node on that row has value 3.
Example 3:
Input: root = [-2]
Output: -2Explanation: The root is the only node, so it is also the leftmost node on the deepest level.
Constraints:
- The tree contains between 1 and 10000 nodes.
-2147483648 <= Node.val <= 2147483647
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Binary Trees - Understanding tree structure, nodes, left/right children, and tree depth concepts
- Breadth-First Search (BFS) - Level-order traversal using a queue to process nodes level by level
- Depth-First Search (DFS) - Recursive and iterative tree traversal techniques with depth tracking
1. Breadth First Search
Intuition
We need the leftmost value in the bottom row of the tree. A clever BFS trick handles this: instead of traversing left-to-right, we process nodes right-to-left. This way, the last node we visit in the entire traversal is exactly the bottom-left node. No need to track levels or compare positions.
Algorithm
- Initialize a queue with the root node.
- While the queue is not empty:
- Dequeue a node.
- Enqueue the right child first (if it exists), then the left child.
- When the loop ends, the last dequeued node is the bottom-left value.
- Return that node's value.
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
q = deque([root])
while q:
node = q.popleft()
if node.right:
q.append(node.right)
if node.left:
q.append(node.left)
return node.valTime & Space Complexity
- Time complexity:
- Space complexity:
2. Depth First Search
Intuition
Using DFS, we track the maximum depth seen so far. Whenever we reach a new maximum depth, we update our result with the current node's value. By visiting the left subtree before the right subtree, the first node we encounter at any given depth is guaranteed to be the leftmost one at that level.
Algorithm
- Initialize
maxDepthto-1andresto the root's value. - Define a recursive DFS function that takes a node and its depth:
- If the node is
null, return. - If the current depth exceeds
maxDepth, updatemaxDepthand store the node's value inres. - Recurse on the left child, then the right child, each with
depth + 1.
- If the node is
- Call DFS starting from the root at depth
0. - Return
res.
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
self.maxDepth, self.res = -1, root.val
def dfs(node, depth):
if not node:
return
if depth > self.maxDepth:
self.maxDepth, self.res = depth, node.val
dfs(node.left, depth + 1)
dfs(node.right, depth + 1)
dfs(root, 0)
return self.resTime & Space Complexity
- Time complexity:
- Space complexity: for recursion stack.
3. Iterative DFS
Intuition
This is the same logic as recursive DFS, but uses an explicit stack instead of the call stack. We push nodes along with their depths and process them in LIFO order. By pushing the right child before the left child, we ensure left children are processed first at each level, maintaining the leftmost-first property.
Algorithm
- Initialize
maxDepthto-1,resto the root's value, and a stack containing(root, 0). - While the stack is not empty:
- Pop
(node, depth)from the stack. - If
depth > maxDepth, updatemaxDepthand setresto the node's value. - Push the right child (if exists) with
depth + 1, then the left child withdepth + 1.
- Pop
- Return
res.
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
res, maxDepth = root.val, -1
stack = [(root, 0)]
while stack:
node, depth = stack.pop()
if depth > maxDepth:
maxDepth = depth
res = node.val
if node.right:
stack.append((node.right, depth + 1))
if node.left:
stack.append((node.left, depth + 1))
return resTime & Space Complexity
- Time complexity:
- Space complexity:
4. Morris Traversal
Intuition
Morris traversal lets us traverse the tree without using extra space for a stack or recursion. We temporarily modify the tree by threading right pointers of predecessor nodes back to their successors, creating a way to return after visiting the left subtree. We track depth manually by counting steps down and back up, updating our result whenever we reach a new maximum depth.
Algorithm
- Initialize
resto the root's value,maxDepthto-1,curDepthto0, andcurto root. - While
curis notnull:- If
curhas no left child:- If
curDepth > maxDepth, updatemaxDepthandres. - Move to the right child and increment
curDepth.
- If
- Otherwise:
- Find the rightmost node in the left subtree (the predecessor), counting steps.
- If the predecessor's right pointer is
null, set it tocur, move left, and incrementcurDepth. - If it points back to
cur, restore it tonull, decrementcurDepthby the step count, and move right.
- If
- Return
res.
# 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 findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
res, maxDepth, curDepth = root.val, -1, 0
cur = root
while cur:
if not cur.left:
if curDepth > maxDepth:
maxDepth, res = curDepth, cur.val
cur = cur.right
curDepth += 1
else:
prev = cur.left
steps = 1
while prev.right and prev.right != cur:
prev = prev.right
steps += 1
if not prev.right:
prev.right = cur
cur = cur.left
curDepth += 1
else:
prev.right = None
curDepth -= steps
cur = cur.right
return resTime & Space Complexity
- Time complexity:
- Space complexity: extra space.
Common Pitfalls
Enqueueing Children in Wrong Order for BFS
The right-to-left BFS trick only works if right children are enqueued before left children. Reversing this order causes the last processed node to be the bottom-right instead of bottom-left.
Not Tracking Depth in DFS
When using DFS, updating the result without checking if the current depth exceeds the maximum seen depth causes incorrect values to be recorded. Always compare depths before updating the result.
Confusing Leftmost with First Encountered
The leftmost value at the deepest level is not necessarily the first node encountered during traversal. In DFS, left-first traversal combined with depth tracking ensures the leftmost node at maximum depth is correctly identified.
Sign in to join the discussion