104. Maximum Depth of Binary Tree
Problem Description
In this LeetCode problem, we are given a binary tree, which is a tree data structure where each node has at most two children, referred to as the left child and the right child. The task is to find the maximum depth of the tree. The maximum depth is defined as the length of the longest path from the root node of the tree down to the farthest leaf node. The length of a path is measured by the number of nodes along that path. In this context, a leaf node is a node with no children, signifying that it's at the edge of the tree.
Flowchart Walkthrough
Let's pinpoint the appropriate algorithm using the Flowchart. Here's a detailed breakdown:
Is it a graph?
- Yes: Although specifically, it's a binary tree, which is a special kind of graph.
Is it a tree?
- Yes: The problem explicitly mentions that the structure is a binary tree.
At this point in the flowchart, the next step after identifying the structure as a tree leads us directly to using Depth First Search (DFS).
Conclusion: The flowchart leads us to utilize DFS for this problem as we've identified the structure as a tree, where Depth First Search is particularly effective in traversing and discovering the maximum depth.
Intuition
To determine the solution to finding the maximum depth of a binary tree, we can use a strategy known as depth-first search (DFS).
The intuition behind the approach is quite straightforward:
- If we start at the root and the tree is empty, the maximum depth is zero.
- If the tree is not empty, we can recursively find the maximum depth of the left and right subtrees.
- The maximum depth of the tree would then be the larger of the two maximum depths found, plus one for the root node itself.
This recursive strategy hinges upon the idea that the depth of a tree is equal to the depth of its deepest subtree, plus a factor of one for the root. As we explore each subtree, we keep on asking the same question, 'what's the maximum depth from this node down?'. We keep on doing this recursively until we reach the leaf nodes, which have a depth of zero since there are no further nodes below them.
The solution leverages this idea and recursively descends through the tree, ensuring that the maximum depth is calculated for each subtree. Once the recursion reaches the leaf nodes, it begins to unwind, cumulatively calculating the depth by comparing the depths of the left and right subtrees at each step, and adding one to account for the current node's depth contribution. By the time the recursion unwinds back to the root, we would have found the maximum depth.
Learn more about Tree, Depth-First Search, Breadth-First Search and Binary Tree patterns.
Solution Approach
The solution provided is an example of a depth-first search (DFS) algorithm implemented using recursion, which is a common pattern for traversing trees. The approach is simple and consists of the following steps:
-
Base Case Check: At the start of the
maxDepth
method, the base case checks if the current node, initially the root, isNone
. If it is, this means that we have hit the bottom of the tree (or the tree is empty to begin with), and we return0
as the depth. Every leaf will eventually hit this base case.if root is None: return 0
-
Recursive Calls: If the node is not
None
, we proceed to make recursive calls for the left child and right child of the currentroot
node.l, r = self.maxDepth(root.left), self.maxDepth(root.right)
By calling
self.maxDepth
onroot.left
androot.right
, we are asking for the maximum depth from each child node. -
Calculating Depth: After receiving the maximum depths from the left and right subtrees (
l
andr
), we calculate the maximum depth of the current tree by taking themax
ofl
andr
, and adding1
to account for the current root node.return 1 + max(l, r)
-
Climbing up the Recursion: This step essentially repeats for each node in the tree until all nodes have been visited, and at each step, we climb up the tree's layers, cumulatively calculating the maximum depth by comparing the depths from the left and right.
The choice of DFS and recursion in this case allows for an elegant and easily understandable solution to the problem. It's worth mentioning that every time a recursive call is made, the call stack keeps track of each node's state, enabling the process to 'remember' the paths taken once it needs to return and subsequently combine the depths found.
Overall, this implementation relies heavily on the nature of recursion to break down the problem into manageable pieces, solve each minor problem, and combine the results to arrive at the final solution, serving as a clear illustration of a divide-and-conquer strategy.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's illustrate the solution approach using a small example of a binary tree. Assume we have the following binary tree:
1 / \ 2 3 / \ \ 4 5 6 / \ 7 8
In this tree, the root node is 1
, and its left child is 2
, and its right child is 3
. Continuing down the tree, 2
has two children 4
and 5
, and 3
has one child 6
. On the next level, 4
has one child 7
and 6
has one child 8
. The leaf nodes in this tree are 5
, 7
, and 8
.
Let's walk through the recursive solution to find the maximum depth of this tree:
-
Starting at the root node (1):
- The
maxDepth
method is called with the root node1
. Since1
is notNone
, we perform the recursive calls on its children (2
and3
).
- The
-
Explore the left subtree of node (2):
- The
maxDepth
method is called on the left child (2
), which is notNone
. Recursive calls are made on its children (4
and5
).
- The
-
Explore the left subtree of node (4):
- Continuing the recursion,
maxDepth
is called on the child4
. It has a left child7
, so another recursive call is made.
- Continuing the recursion,
-
Leaf node (7):
- The
maxDepth
method is called on7
. With no children,7
is a leaf node, reaching the base case. Hence, it returns0
, indicating a node's depth is zero below it.
- The
-
Climb up from leaf node (7) to node (4):
- Since
4
has no right child, it compares the depths of left (0+1
) and (non-existent) right subtrees and returns1
.
- Since
-
Climb up to node (2):
- Now we consider node
5
, which is a leaf node. It hits the base case and returns0
. - Back at node
2
, we compare the maximum depths received from4
(1
) and5
(0
), and add1
for the node2
itself. So2
returns2
.
- Now we consider node
-
Explore the right subtree of node (3):
- The recursion calls
maxDepth
on node3
. It has a right child6
, so we callmaxDepth
on6
.
- The recursion calls
-
Explore the right subtree of node (6):
- Node
6
has a right child8
, and invokingmaxDepth
on8
leads to a base case returning0
.
- Node
-
Climb up from leaf node (8) to node (6):
- Node
6
has no left child, so it takes the maximum of left (non-existent) and right (0+1
) depths, resulting in1
.
- Node
-
Climb up to node (3):
- At node
3
, we compare the maximum depths from6
(1
), and since3
has no left child, we conclude node3
's subtree has a maximum depth of2
(1
from6
plus1
for the3
itself).
- At node
-
Combine results at the root node (1):
- Finally, at the root node
1
, we compare the depths received from2
(2
) and3
(2
), taking the maximum, which is2
, and add1
for the root node. Hence, we determine that the maximum depth of the tree is3
.
- Finally, at the root node
This recursive process explored each subtree, remembered the maximum depth at each level, and combined the results to deliver the overall maximum depth.
Solution Implementation
1# Definition for a binary tree node.
2class TreeNode:
3 def __init__(self, val=0, left=None, right=None):
4 self.val = val
5 self.left = left
6 self.right = right
7
8class Solution:
9 def maxDepth(self, root: TreeNode) -> int:
10 # If the current node is None, it means this is an empty tree or we've reached the end of a branch.
11 # Return 0 in this case as it contributes no depth.
12 if root is None:
13 return 0
14
15 # Recursively find the depth of the left subtree.
16 # This will traverse all the way down to the leftmost leaf node.
17 left_depth = self.maxDepth(root.left)
18
19 # Recursively find the depth of the right subtree.
20 # This will traverse all the way down to the rightmost leaf node.
21 right_depth = self.maxDepth(root.right)
22
23 # The maximum depth of the current node will be 1 (for the current node) plus the maximum
24 # of the depths of its left and right subtrees.
25 # This ensures that the longest path from root to leaf is counted.
26 return 1 + max(left_depth, right_depth)
27
1// Definition for a binary tree node.
2class TreeNode {
3 int value;
4 TreeNode left;
5 TreeNode right;
6
7 // Constructor to create a leaf node.
8 TreeNode(int value) {
9 this.value = value;
10 }
11
12 // Constructor to create a node with specific children.
13 TreeNode(int value, TreeNode left, TreeNode right) {
14 this.value = value;
15 this.left = left;
16 this.right = right;
17 }
18}
19
20class Solution {
21 // Calculates the maximum depth of a binary tree.
22 public int maxDepth(TreeNode root) {
23 // If the root is null, the depth is 0.
24 if (root == null) {
25 return 0;
26 }
27
28 // Recursively compute the depth of the left subtree.
29 int leftDepth = maxDepth(root.left);
30
31 // Recursively compute the depth of the right subtree.
32 int rightDepth = maxDepth(root.right);
33
34 // The depth of the current node is the greater of its two children's depths plus one.
35 return 1 + Math.max(leftDepth, rightDepth);
36 }
37}
38
1// Definition for a binary tree node.
2struct TreeNode {
3 int val; // value of the node
4 TreeNode *left; // pointer to the left child
5 TreeNode *right; // pointer to the right child
6
7 // Constructor to initialize the node with a value, and nullptr for children
8 TreeNode() : val(0), left(nullptr), right(nullptr) {}
9
10 // Constructor to initialize the node with a value and no children
11 TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
12
13 // Constructor to initialize the node with a value and left and right children
14 TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
15};
16
17class Solution {
18public:
19 // Function to find the maximum depth of a binary tree.
20 int maxDepth(TreeNode* root) {
21 // Base case: if the current node is null, return 0 as the depth
22 if (!root)
23 return 0;
24
25 // Recursively compute the depth of the left subtree
26 int leftDepth = maxDepth(root->left);
27 // Recursively compute the depth of the right subtree
28 int rightDepth = maxDepth(root->right);
29
30 // The maximum depth of the current node is 1 (for the current node) plus
31 // the greater depth between the left and right subtrees
32 return 1 + std::max(leftDepth, rightDepth);
33 }
34};
35
1/**
2 * Definition for a binary tree node.
3 */
4interface TreeNode {
5 val: number;
6 left: TreeNode | null;
7 right: TreeNode | null;
8}
9
10/**
11 * This function computes the maximum depth of a binary tree.
12 * The maximum depth is the number of nodes along the longest path
13 * from the root node down to the farthest leaf node.
14 *
15 * @param {TreeNode | null} root - The root node of the binary tree.
16 * @return {number} The depth of the binary tree.
17 */
18function maxDepth(root: TreeNode | null): number {
19 // An empty tree has a depth of zero
20 if (root === null) {
21 return 0;
22 }
23 // Recursively compute the depth of the left and right subtree
24 // and return the greater one increased by 1 for the current node
25 return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
26}
27
Time and Space Complexity
Time Complexity: The time complexity of the code is O(n)
, where n
is the number of nodes in the tree. This is because the algorithm is a depth-first search, and it visits each node exactly once to determine the maximum depth.
Space Complexity: The space complexity of the code is O(h)
, where h
is the height of the tree. This space is used by the call stack during the recursion. In the worst case, if the tree is completely unbalanced, with all nodes on one side, the height of the tree h
can be equal to n
, leading to a space complexity of O(n)
. In the best case, for a completely balanced tree, the height h
would be log(n)
, leading to a space complexity of O(log(n))
.
Learn more about how to find time and space complexity quickly using problem constraints.
In a binary min heap, the maximum element can be found in:
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 algomonster s3 us east 2 amazonaws com 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
https algomonster s3 us east 2 amazonaws com 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
Want a Structured Path to Master System Design Too? Don’t Miss This!