671. Second Minimum Node In a Binary Tree
Problem Description
You are given a special binary tree with the following properties:
-
Every node has exactly 2 children or no children (it's either a leaf node with 0 children or an internal node with 2 children)
-
All node values are non-negative integers
-
Parent-child value relationship: If a node has two children, its value equals the minimum of its two children's values. In other words,
parent.val = min(left_child.val, right_child.val)
Your task is to find the second smallest value among all the values in the entire tree.
Key Points:
- The root node will always contain the smallest value in the tree (due to the min-heap property)
- You need to find the next smallest distinct value after the root's value
- If there is no second minimum value (for example, if all nodes have the same value), return
-1
Example scenarios:
- If the tree has values like
[2, 2, 5, 5, 7]
, the minimum is2
and the second minimum is5
- If all nodes have the same value, there's no second minimum, so return
-1
The solution uses a depth-first search (DFS) to traverse the tree and track the smallest value greater than the root's value. Since the root contains the global minimum, any value larger than the root is a candidate for the second minimum. The algorithm compares all such candidates and returns the smallest one.
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 with nodes connected in a hierarchical manner without cycles.
DFS
- Conclusion reached: Since we're dealing with a tree structure, the flowchart directly points us to use DFS (Depth-First Search).
The flowchart analysis confirms that DFS is the appropriate approach for this problem. This makes sense because:
- We need to traverse the entire tree to examine all node values
- We're looking for a specific value (second minimum) that could be anywhere in the tree
- DFS allows us to systematically explore each branch of the tree from root to leaves
- The tree structure naturally lends itself to recursive DFS implementation
In the given solution, DFS is implemented recursively to:
- Visit every node in the tree
- Compare each node's value with the root value (which is the minimum)
- Track the smallest value that is greater than the root value (which becomes our second minimum)
Conclusion: The flowchart correctly guides us to use DFS for traversing the binary tree to find the second minimum value.
Intuition
The key insight comes from understanding the special property of this tree: the root always contains the minimum value of the entire tree. This is because each parent node equals the minimum of its children, creating a min-heap-like structure where values can only increase or stay the same as we go down the tree.
Since we know the root is the smallest value, finding the second minimum becomes straightforward: we need to find the smallest value that is strictly greater than the root's value.
Think about it this way:
- The root value
root.val
is guaranteed to be the global minimum - Any node with value >
root.val
is a candidate for being the second minimum - We just need to find the smallest among all these candidates
Why does DFS work perfectly here? Because we need to examine every single node in the tree to ensure we don't miss any potential second minimum value. A node deep in the tree might have the second smallest value, so we can't stop early.
The algorithm naturally follows:
- Remember the root value as our baseline (the minimum)
- Traverse the entire tree using DFS
- For each node encountered, if its value is greater than the root value, it's a candidate
- Keep track of the smallest candidate seen so far
- After traversing all nodes, the smallest candidate is our answer
The beauty of this approach is its simplicity - we're essentially doing a single pass through the tree looking for the smallest value that's bigger than what we already know is the minimum. If we never find such a value (all nodes equal the root), we return -1
.
Learn more about Tree, Depth-First Search and Binary Tree patterns.
Solution Approach
The implementation uses a recursive DFS traversal with a simple tracking mechanism for the second minimum value.
Data Structures Used:
- Two variables:
ans
to store the second minimum (initialized to-1
) andv
to store the root value (the global minimum) - The
nonlocal
keyword in Python to modify outer scope variables from within the nested function
Algorithm Steps:
-
Initialize tracking variables:
ans, v = -1, root.val
v
stores the root value (guaranteed minimum)ans
starts at-1
(our default return value if no second minimum exists)
-
Define the DFS function:
def dfs(root): if root: dfs(root.left) dfs(root.right) # Process current node after visiting children
The DFS recursively visits left subtree, then right subtree, then processes the current node.
-
Core logic for finding second minimum:
if root.val > v: ans = root.val if ans == -1 else min(ans, root.val)
- Only consider nodes with values greater than the minimum (
root.val > v
) - If
ans
is still-1
(first candidate found), set it to the current node's value - Otherwise, keep the smaller value between the current
ans
and the new candidate
- Only consider nodes with values greater than the minimum (
Why this works:
- Since we traverse every node, we're guaranteed to examine all possible candidates
- The condition
root.val > v
filters out all nodes equal to the minimum - The
min()
operation ensures we always keep track of the smallest valid candidate - If no node has a value greater than the root,
ans
remains-1
Time Complexity: O(n)
where n
is the number of nodes, as we visit each node exactly once.
Space Complexity: O(h)
where h
is the height of the tree, due to the recursive call stack.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through the algorithm with a concrete example tree:
2 / \ 2 5 / \ 5 7
Step 1: Initialize variables
ans = -1
(no second minimum found yet)v = 2
(root value, which is the global minimum)
Step 2: Start DFS traversal from root (value = 2)
The DFS visits nodes in this order:
-
Visit root (2)
- Recursively visit left child
-
Visit left child (2)
- This is a leaf, so no children to visit
- Check: Is
2 > v
(i.e.,2 > 2
)? No, so skip this node - Return to parent
-
Back at root, now visit right child (5)
- Recursively visit its left child
-
Visit node (5) - left child of node (5)
- This is a leaf
- Check: Is
5 > v
(i.e.,5 > 2
)? Yes! - Since
ans == -1
, setans = 5
- Return to parent
-
Back at node (5), visit right child (7)
- This is a leaf
- Check: Is
7 > v
(i.e.,7 > 2
)? Yes! - Since
ans == 5
, computemin(5, 7) = 5
- Keep
ans = 5
- Return to parent
-
Back at node (5) - process this node
- Check: Is
5 > v
(i.e.,5 > 2
)? Yes! - Since
ans == 5
, computemin(5, 5) = 5
- Keep
ans = 5
- Check: Is
Step 3: DFS complete, return result
- Return
ans = 5
The second smallest value in the tree is 5.
Key observations from this walkthrough:
- Node with value 2 (equal to root) was skipped
- All nodes with values greater than 2 were considered (both 5s and the 7)
- The algorithm correctly identified 5 as the smallest value greater than the minimum (2)
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 typing import Optional
9
10class Solution:
11 def findSecondMinimumValue(self, root: Optional[TreeNode]) -> int:
12 """
13 Find the second minimum value in a special binary tree.
14 In this tree, each node has exactly 0 or 2 children, and
15 the value of a parent is the minimum of its two children.
16
17 Args:
18 root: The root node of the binary tree
19
20 Returns:
21 The second minimum value in the tree, or -1 if it doesn't exist
22 """
23
24 def dfs(node: Optional[TreeNode]) -> None:
25 """
26 Depth-first search to traverse the tree and find the second minimum value.
27
28 Args:
29 node: Current node being processed
30 """
31 # Base case: if node is None, return
32 if not node:
33 return
34
35 # Recursively traverse left and right subtrees
36 dfs(node.left)
37 dfs(node.right)
38
39 # Access outer scope variables
40 nonlocal second_min, first_min
41
42 # If current node value is greater than the minimum (root value),
43 # it's a candidate for second minimum
44 if node.val > first_min:
45 # Update second_min: either set it for first time or take minimum
46 if second_min == -1:
47 second_min = node.val
48 else:
49 second_min = min(second_min, node.val)
50
51 # Initialize variables
52 # second_min: stores the second minimum value (-1 if not found)
53 # first_min: stores the minimum value (which is always the root value)
54 second_min = -1
55 first_min = root.val
56
57 # Start DFS traversal from root
58 dfs(root)
59
60 # Return the second minimum value
61 return second_min
62
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 // Instance variable to store the second minimum value
18 private int secondMinValue = -1;
19
20 /**
21 * Finds the second minimum value in a binary tree.
22 * The tree has a special property where each node has either 0 or 2 children,
23 * and if a node has two children, its value is the smaller value among its children.
24 *
25 * @param root The root node of the binary tree
26 * @return The second minimum value in the tree, or -1 if it doesn't exist
27 */
28 public int findSecondMinimumValue(TreeNode root) {
29 // The root value is always the minimum value in this special tree
30 int minValue = root.val;
31
32 // Perform depth-first search to find the second minimum value
33 dfs(root, minValue);
34
35 return secondMinValue;
36 }
37
38 /**
39 * Performs a depth-first search to find the second minimum value.
40 *
41 * @param node The current node being processed
42 * @param minValue The minimum value in the tree (root's value)
43 */
44 private void dfs(TreeNode node, int minValue) {
45 // Base case: if node is null, return
46 if (node == null) {
47 return;
48 }
49
50 // Recursively traverse the left subtree
51 dfs(node.left, minValue);
52
53 // Recursively traverse the right subtree
54 dfs(node.right, minValue);
55
56 // If current node's value is greater than the minimum value,
57 // it's a candidate for the second minimum value
58 if (node.val > minValue) {
59 // Update secondMinValue if it hasn't been set yet or if we found a smaller candidate
60 if (secondMinValue == -1) {
61 secondMinValue = node.val;
62 } else {
63 secondMinValue = Math.min(secondMinValue, node.val);
64 }
65 }
66 }
67}
68
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 findSecondMinimumValue(TreeNode* root) {
15 // Initialize the second minimum value as -1 (indicating not found)
16 int secondMin = -1;
17
18 // Start DFS traversal with the root's value as the minimum
19 dfs(root, root->val, secondMin);
20
21 return secondMin;
22 }
23
24private:
25 /**
26 * Performs depth-first search to find the second minimum value in the tree
27 * @param node - current node being processed
28 * @param minVal - the minimum value in the tree (root's value)
29 * @param secondMin - reference to store the second minimum value found
30 */
31 void dfs(TreeNode* node, int minVal, int& secondMin) {
32 // Base case: if node is null, return
33 if (!node) {
34 return;
35 }
36
37 // Recursively traverse left subtree
38 dfs(node->left, minVal, secondMin);
39
40 // Recursively traverse right subtree
41 dfs(node->right, minVal, secondMin);
42
43 // If current node's value is greater than the minimum value,
44 // it's a candidate for the second minimum
45 if (node->val > minVal) {
46 // Update secondMin: if not set yet, use current value;
47 // otherwise, keep the smaller of the two
48 secondMin = (secondMin == -1) ? node->val : min(secondMin, node->val);
49 }
50 }
51};
52
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 * Finds the second minimum value in a special binary tree where each node has exactly 0 or 2 children
12 * and the value of a parent node is the minimum of its two children
13 * @param root - The root node of the binary tree
14 * @returns The second minimum value in the tree, or -1 if it doesn't exist
15 */
16function findSecondMinimumValue(root: TreeNode | null): number {
17 // Initialize result as -1 (indicates no second minimum found)
18 let secondMinimum: number = -1;
19
20 // Store the root value (which is the minimum value in the tree)
21 const minimumValue: number = root!.val;
22
23 /**
24 * Depth-first search to traverse the tree and find the second minimum value
25 * @param node - Current node being processed
26 */
27 function dfs(node: TreeNode | null): void {
28 // Base case: if node is null, return
29 if (!node) {
30 return;
31 }
32
33 // Recursively traverse left subtree
34 dfs(node.left);
35
36 // Recursively traverse right subtree
37 dfs(node.right);
38
39 // Check if current node value is greater than the minimum value
40 if (node.val > minimumValue) {
41 // Update secondMinimum if it hasn't been set or if current value is smaller
42 if (secondMinimum === -1 || secondMinimum > node.val) {
43 secondMinimum = node.val;
44 }
45 }
46 }
47
48 // Start DFS traversal from root
49 dfs(root);
50
51 // Return the second minimum value (or -1 if not found)
52 return secondMinimum;
53}
54
Time and Space Complexity
Time Complexity: O(n)
where n
is the number of nodes in the binary tree. The DFS traversal visits each node exactly once, performing constant time operations at each node (comparison and assignment operations).
Space Complexity: O(h)
where h
is the height of the binary tree. This space is used by the recursive call stack during the DFS traversal. In the worst case of a skewed tree, this could be O(n)
, while in the best case of a balanced tree, it would be O(log n)
. The additional space used for the variables ans
and v
is O(1)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Early Termination Optimization Leading to Incorrect Results
A common pitfall is trying to optimize the traversal by stopping early when we think we've found the answer. For example:
Incorrect Approach:
def findSecondMinimumValue(self, root: Optional[TreeNode]) -> int:
def dfs(node):
if not node:
return
nonlocal second_min, first_min
# WRONG: Early termination when we find a value > first_min
if node.val > first_min:
second_min = node.val
return # Stop searching - THIS IS WRONG!
dfs(node.left)
dfs(node.right)
second_min = -1
first_min = root.val
dfs(root)
return second_min
Why this fails: Due to the tree's special property where parent.val = min(left.val, right.val)
, the second minimum value might not be at the shallowest level. Consider this tree:
2 / \ 2 5 / \ 5 3
If we stop at the first node with value > 2 (which is 5), we miss the actual second minimum value 3.
Solution: Always traverse the entire tree to ensure we find the global second minimum.
2. Incorrect Handling of Duplicate Values
Another pitfall is not properly handling trees where multiple nodes have the minimum value:
Incorrect Approach:
def findSecondMinimumValue(self, root: Optional[TreeNode]) -> int:
def dfs(node):
if not node:
return
nonlocal second_min
# WRONG: Not checking if value is actually greater than minimum
if node.val != root.val: # This seems logical but...
if second_min == -1:
second_min = node.val
else:
second_min = min(second_min, node.val)
dfs(node.left)
dfs(node.right)
Why this might seem correct but has issues: While node.val != root.val
is equivalent to node.val > root.val
in this specific tree structure (since parent = min of children), using inequality comparison makes the logic clearer and more maintainable.
3. Forgetting the Tree's Special Properties
A critical pitfall is attempting pruning optimizations without fully understanding the tree structure:
Incorrect Pruning:
def findSecondMinimumValue(self, root: Optional[TreeNode]) -> int:
def dfs(node):
if not node:
return
nonlocal second_min, first_min
# WRONG: Assuming we can skip subtrees
if node.val == first_min:
# Skip this subtree - INCORRECT!
return
if node.val > first_min:
second_min = node.val if second_min == -1 else min(second_min, node.val)
dfs(node.left)
dfs(node.right)
Why this fails: Even if a node has the minimum value, its children might have different values that could be candidates for the second minimum. The tree property only guarantees parent = min(children), not that all descendants have the same value.
Correct Solution: The provided solution avoids all these pitfalls by:
- Traversing the entire tree without early termination
- Using clear comparison logic (
node.val > first_min
) - Not making assumptions about subtrees based on parent values
Which of the following uses divide and conquer strategy?
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
Binary Tree Min Depth Prereq BFS on Tree problems bfs_intro Given a binary tree find the depth of the shallowest leaf node https assets algo monster binary_tree_min_depth png Explanation We can solve this problem with either DFS or BFS With DFS we traverse the whole tree looking for leaf nodes and record and update the minimum depth as we go With BFS though since we search level by level we are guaranteed to find the shallowest leaf node
Want a Structured Path to Master System Design Too? Don’t Miss This!