513. Find Bottom Left Tree Value
Problem Description
You are given the root of a binary tree. Your task is to find the value of the leftmost node in the bottom-most (last) row of the tree.
The problem asks you to:
- Identify the last row of the binary tree (the deepest level)
- Find the leftmost node in that row
- Return the value of that node
For example, if you have a binary tree like:
1 / \ 2 3 / / \ 4 5 6
The last row contains nodes 4
, 5
, and 6
. The leftmost value in this row is 4
, so you would return 4
.
The solution uses a level-order traversal (BFS) approach with a queue. It processes the tree level by level, and for each level, it captures the first node's value (which is the leftmost node of that level). By the time the traversal completes, ans
will hold the leftmost value of the last row encountered.
The key insight is that when using BFS with a queue, the first element in the queue at the start of processing each level is always the leftmost node of that level. The algorithm keeps updating ans
with this leftmost value for each level, so when it finishes, ans
contains the leftmost value of the deepest level.
Flowchart Walkthrough
First, let's pin down the algorithm using the Flowchart. Here's a step-by-step walkthrough:
Is it a graph?
- Yes: A binary tree is a special type of graph where each node has at most two children and there are no cycles.
Is it a tree?
- Yes: The problem explicitly states we're working with a binary tree structure.
DFS
- Yes: We arrive at DFS as the recommended approach for tree problems.
Conclusion: The flowchart suggests using DFS (Depth-First Search) for this tree problem.
While the provided solution uses BFS (level-order traversal with a queue), DFS is equally valid and follows the flowchart's recommendation. With DFS, we can traverse the tree while tracking the depth of each node. We maintain variables for the maximum depth seen so far and the leftmost value at that depth. When we encounter a node at a deeper level than previously seen, or when we encounter the first node at the current maximum depth (which would be the leftmost due to the traversal order), we update our answer.
The DFS approach would involve:
- Traversing the tree recursively
- Tracking the current depth during traversal
- Updating the leftmost value whenever we reach a new maximum depth
- Prioritizing left subtree traversal before right to ensure we capture the leftmost node first at each level
Both DFS and BFS are valid approaches for this problem, with the flowchart naturally leading us to consider DFS for tree-based problems.
Intuition
The key insight is that we need to find the leftmost value in the deepest level of the tree. This naturally suggests we need to explore the tree level by level to identify which level is the deepest, and within that level, which node appears first from the left.
When we think about traversing a tree level by level, BFS (Breadth-First Search) immediately comes to mind because it processes all nodes at depth d
before moving to nodes at depth d+1
. This gives us a natural way to identify the last level - it's simply the final set of nodes we process.
For each level during BFS traversal, the nodes are processed from left to right because:
- We add the left child before the right child to the queue
- The queue maintains this left-to-right ordering naturally
The clever part of the solution is recognizing that at the start of processing each level, q[0]
(the first element in the queue) is always the leftmost node of that level. Instead of storing all nodes of each level or tracking which level is the last, we simply update our answer with q[0].val
at the beginning of each level's processing.
By continuously updating ans
with the leftmost value of each level as we traverse, we guarantee that when the traversal completes, ans
holds the leftmost value of the deepest level we encountered. This is elegant because:
- We don't need to explicitly track the depth
- We don't need to store all values of the last level
- We get the answer in a single pass through the tree
The solution essentially transforms the problem from "find the leftmost value in the last row" to "keep track of the leftmost value of the current level, and the final value will be from the last row."
Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.
Solution Approach
The solution implements a level-order traversal using BFS with a queue data structure. Here's how the implementation works:
-
Initialize the Queue: Start with a deque containing just the root node:
q = deque([root])
. We use a deque for efficient pop operations from the left. -
Initialize the Answer: Set
ans = 0
to store the leftmost value of the current level being processed. -
Process Each Level: The outer
while q:
loop continues as long as there are nodes to process. Each iteration of this loop represents processing one complete level of the tree. -
Capture Leftmost Value: At the start of processing each level, immediately capture the value of the leftmost node:
ans = q[0].val
. This is the key step -q[0]
is always the leftmost node of the current level. -
Process All Nodes in Current Level: The inner
for _ in range(len(q)):
loop ensures we process exactly the nodes that belong to the current level. Thelen(q)
at the start of the level tells us how many nodes are in this level. -
Add Children for Next Level: For each node in the current level:
- Pop the node from the left:
node = q.popleft()
- Add its children to the queue (left child first, then right child):
if node.left: q.append(node.left) if node.right: q.append(node.right)
- The order matters here - adding left before right ensures the next level maintains left-to-right ordering.
- Pop the node from the left:
-
Return the Final Answer: After all levels are processed,
ans
contains the leftmost value from the last level encountered, which is exactly what we need to return.
The algorithm's time complexity is O(n)
where n
is the number of nodes, as we visit each node exactly once. The space complexity is O(w)
where w
is the maximum width of the tree, which represents the maximum number of nodes at any level stored in the queue at once.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a small binary tree example:
2 / \ 1 3 / 4 / \ 5 6
We want to find the leftmost value in the bottom row. The bottom row here is [5, 6]
, so our answer should be 5
.
Initial State:
- Queue:
[2]
(contains root) - ans:
0
Level 1 (root level):
- Before processing:
q = [2]
- Update ans:
ans = q[0].val = 2
(leftmost of this level) - Process node 2:
- Pop 2 from queue
- Add children: left child 1, right child 3
- After level:
q = [1, 3]
Level 2:
- Before processing:
q = [1, 3]
- Update ans:
ans = q[0].val = 1
(leftmost of this level) - Process 2 nodes (original queue length):
- Process node 1:
- Pop 1 from queue
- Add child: left child 4
- Process node 3:
- Pop 3 from queue
- No children to add
- Process node 1:
- After level:
q = [4]
Level 3:
- Before processing:
q = [4]
- Update ans:
ans = q[0].val = 4
(leftmost of this level) - Process 1 node:
- Process node 4:
- Pop 4 from queue
- Add children: left child 5, right child 6
- Process node 4:
- After level:
q = [5, 6]
Level 4 (last level):
- Before processing:
q = [5, 6]
- Update ans:
ans = q[0].val = 5
(leftmost of this level) - Process 2 nodes:
- Process node 5:
- Pop 5 from queue
- No children to add
- Process node 6:
- Pop 6 from queue
- No children to add
- Process node 5:
- After level:
q = []
(empty)
Result:
The queue is empty, so we exit the loop. The variable ans = 5
, which is indeed the leftmost value in the bottom row of the tree.
The key insight demonstrated here is that at the start of processing each level, q[0]
always gives us the leftmost node of that level. By continuously updating ans
with this value, we ensure that after processing all levels, ans
contains the leftmost value from the deepest level.
Solution Implementation
1# Definition for a binary tree node.
2# class TreeNode:
3# def __init__(self, val=0, left=None, right=None):
4# self.val = val
5# self.left = left
6# self.right = right
7
8from collections import deque
9from typing import Optional
10
11class Solution:
12 def findBottomLeftValue(self, root: Optional[TreeNode]) -> int:
13 """
14 Find the leftmost value in the bottom row of a binary tree.
15 Uses BFS (level-order traversal) to process the tree level by level.
16
17 Args:
18 root: The root node of the binary tree
19
20 Returns:
21 The value of the leftmost node in the bottom row
22 """
23 # Initialize queue with root node for BFS traversal
24 queue = deque([root])
25
26 # Store the leftmost value of the current level
27 leftmost_value = 0
28
29 # Process tree level by level
30 while queue:
31 # Capture the leftmost node's value at the start of each level
32 leftmost_value = queue[0].val
33
34 # Process all nodes in the current level
35 level_size = len(queue)
36 for _ in range(level_size):
37 # Remove and process the front node
38 current_node = queue.popleft()
39
40 # Add left child to queue if it exists
41 if current_node.left:
42 queue.append(current_node.left)
43
44 # Add right child to queue if it exists
45 if current_node.right:
46 queue.append(current_node.right)
47
48 # Return the leftmost value from the last level processed
49 return leftmost_value
50
1/**
2 * Definition for a binary tree node.
3 * public class TreeNode {
4 * int val;
5 * TreeNode left;
6 * TreeNode right;
7 * TreeNode() {}
8 * TreeNode(int val) { this.val = val; }
9 * TreeNode(int val, TreeNode left, TreeNode right) {
10 * this.val = val;
11 * this.left = left;
12 * this.right = right;
13 * }
14 * }
15 */
16class Solution {
17 /**
18 * Finds the leftmost value in the bottom row of a binary tree.
19 * Uses level-order traversal (BFS) to process the tree level by level.
20 *
21 * @param root The root node of the binary tree
22 * @return The value of the leftmost node in the bottom row
23 */
24 public int findBottomLeftValue(TreeNode root) {
25 // Initialize queue for BFS traversal
26 Queue<TreeNode> queue = new ArrayDeque<>();
27 queue.offer(root);
28
29 // Variable to store the leftmost value of current level
30 int bottomLeftValue = 0;
31
32 // Process tree level by level
33 while (!queue.isEmpty()) {
34 // Store the first node's value of current level (leftmost node)
35 bottomLeftValue = queue.peek().val;
36
37 // Get the number of nodes in current level
38 int levelSize = queue.size();
39
40 // Process all nodes in current level
41 for (int i = 0; i < levelSize; i++) {
42 TreeNode currentNode = queue.poll();
43
44 // Add left child to queue for next level processing
45 if (currentNode.left != null) {
46 queue.offer(currentNode.left);
47 }
48
49 // Add right child to queue for next level processing
50 if (currentNode.right != null) {
51 queue.offer(currentNode.right);
52 }
53 }
54 }
55
56 // Return the leftmost value from the last processed level (bottom row)
57 return bottomLeftValue;
58 }
59}
60
1/**
2 * Definition for a binary tree node.
3 * struct TreeNode {
4 * int val;
5 * TreeNode *left;
6 * TreeNode *right;
7 * TreeNode() : val(0), left(nullptr), right(nullptr) {}
8 * TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
9 * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
10 * };
11 */
12class Solution {
13public:
14 int findBottomLeftValue(TreeNode* root) {
15 // Initialize queue for BFS traversal with root node
16 queue<TreeNode*> nodeQueue;
17 nodeQueue.push(root);
18
19 // Variable to store the leftmost value of current level
20 int bottomLeftValue = 0;
21
22 // Perform level-order traversal using BFS
23 while (!nodeQueue.empty()) {
24 // Capture the leftmost node value at the beginning of each level
25 bottomLeftValue = nodeQueue.front()->val;
26
27 // Get the number of nodes in current level
28 int currentLevelSize = nodeQueue.size();
29
30 // Process all nodes in the current level
31 for (int i = 0; i < currentLevelSize; ++i) {
32 // Get and remove the front node from queue
33 TreeNode* currentNode = nodeQueue.front();
34 nodeQueue.pop();
35
36 // Add left child to queue for next level processing
37 if (currentNode->left != nullptr) {
38 nodeQueue.push(currentNode->left);
39 }
40
41 // Add right child to queue for next level processing
42 if (currentNode->right != nullptr) {
43 nodeQueue.push(currentNode->right);
44 }
45 }
46 }
47
48 // Return the leftmost value from the bottom level
49 return bottomLeftValue;
50 }
51};
52
1/**
2 * Definition for a binary tree node.
3 * class TreeNode {
4 * val: number
5 * left: TreeNode | null
6 * right: TreeNode | null
7 * constructor(val?: number, left?: TreeNode | null, right?: TreeNode | null) {
8 * this.val = (val===undefined ? 0 : val)
9 * this.left = (left===undefined ? null : left)
10 * this.right = (right===undefined ? null : right)
11 * }
12 * }
13 */
14
15/**
16 * Finds the value of the bottom-left node in a binary tree.
17 * Uses BFS (level-order traversal) to traverse the tree level by level.
18 * The first node of the last level will be the bottom-left node.
19 *
20 * @param root - The root node of the binary tree
21 * @returns The value of the bottom-left node
22 */
23function findBottomLeftValue(root: TreeNode | null): number {
24 // Initialize result with 0 (will be updated with actual bottom-left value)
25 let bottomLeftValue: number = 0;
26
27 // Initialize queue for BFS traversal with root node
28 const queue: TreeNode[] = [root!];
29
30 // Process nodes level by level
31 while (queue.length > 0) {
32 // Store the value of the first node in current level (leftmost node)
33 bottomLeftValue = queue[0].val;
34
35 // Get the number of nodes in current level
36 const currentLevelSize: number = queue.length;
37
38 // Process all nodes in current level
39 for (let i = 0; i < currentLevelSize; i++) {
40 // Dequeue the front node
41 const currentNode: TreeNode = queue.shift()!;
42
43 // Add left child to queue if it exists
44 if (currentNode.left !== null) {
45 queue.push(currentNode.left);
46 }
47
48 // Add right child to queue if it exists
49 if (currentNode.right !== null) {
50 queue.push(currentNode.right);
51 }
52 }
53 }
54
55 // Return the value of the bottom-left node
56 return bottomLeftValue;
57}
58
Time and Space Complexity
Time Complexity: O(n)
where n
is the total number of nodes in the binary tree. The algorithm performs a level-order traversal (BFS) visiting each node exactly once. Each node is enqueued and dequeued once, and both operations take O(1)
time. Therefore, the overall time complexity is O(n)
.
Space Complexity: O(w)
where w
is the maximum width of the binary tree (the maximum number of nodes at any level). In the worst case, this occurs at the last level of a complete binary tree, where the width can be up to n/2
nodes, making the space complexity O(n)
in the worst case. In the best case (a skewed tree), the space complexity would be O(1)
as there would be at most one node at each level.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Preserving Level Boundaries
A common mistake is trying to find the leftmost value without properly tracking level boundaries. Some might attempt to simply traverse the tree and keep track of the "deepest leftmost" node without using proper level-order traversal:
Incorrect Approach:
def findBottomLeftValue(self, root):
queue = deque([root])
leftmost = root.val
while queue:
node = queue.popleft()
# This doesn't guarantee we're getting the leftmost of the bottom row
if node.left:
queue.append(node.left)
leftmost = node.left.val
if node.right:
queue.append(node.right)
if not node.left: # Wrong logic
leftmost = node.right.val
return leftmost
Why it fails: This approach doesn't properly identify which nodes belong to the last level, and the logic for determining "leftmost" is flawed.
2. Incorrect Order of Child Addition
Adding children in the wrong order (right before left) will break the left-to-right ordering in the queue:
Incorrect Code:
# Wrong order - adds right child before left child if current_node.right: queue.append(current_node.right) if current_node.left: queue.append(current_node.left)
Solution: Always add the left child first, then the right child to maintain proper left-to-right ordering in each level.
3. Capturing Leftmost Value at Wrong Time
Some might try to capture the leftmost value after processing nodes or at the wrong point in the iteration:
Incorrect Timing:
while queue:
level_size = len(queue)
for i in range(level_size):
current_node = queue.popleft()
if i == 0: # Capturing after popleft - node is already removed!
leftmost_value = current_node.val
# ... rest of code
Solution: Capture queue[0].val
before starting to process (popleft) any nodes in the current level. This ensures you're getting the actual leftmost node that's still in the queue.
4. Using Wrong Queue Operations
Using a regular list with pop(0)
instead of deque
with popleft()
:
Inefficient Code:
queue = [root] # Regular list while queue: node = queue.pop(0) # O(n) operation!
Solution: Use collections.deque
with popleft()
for O(1) removal from the front of the queue, making the overall algorithm more efficient.
5. Forgetting Edge Cases
Not handling the guaranteed constraint that root is not None. While the problem guarantees a non-null root, defensive programming might lead to unnecessary checks:
Overly Defensive:
def findBottomLeftValue(self, root):
if not root: # Unnecessary given problem constraints
return 0 # or raise exception
# ... rest of code
Solution: Trust the problem constraints. The problem states the tree has at least one node, so the root will never be None. Focus on the core algorithm instead of adding unnecessary edge case handling.
Depth first search is equivalent to which of the tree traversal order?
Recommended Readings
Everything About Trees A tree is a type of graph data structure composed of nodes and edges Its main properties are It is acyclic doesn't contain any cycles There exists a path from the root to any node Has N 1 edges where N is the number of nodes in the tree and
https assets algo monster cover_photos dfs svg Depth First Search Prereqs Recursion Review problems recursion_intro Trees problems tree_intro With a solid understanding of recursion under our belts we are now ready to tackle one of the most useful techniques in coding interviews Depth First Search DFS As the name suggests
https assets algo monster cover_photos bfs svg Breadth First Search on Trees Hopefully by this time you've drunk enough DFS Kool Aid to understand its immense power and seen enough visualization to create a call stack in your mind Now let me introduce the companion spell Breadth First Search BFS
Want a Structured Path to Master System Design Too? Don’t Miss This!