2058. Find the Minimum and Maximum Number of Nodes Between Critical Points
Problem Description
This problem asks you to find critical points in a linked list and calculate distances between them.
A critical point is a node that is either a local maxima or local minima:
- A local maxima is a node whose value is strictly greater than both its previous and next nodes
- A local minima is a node whose value is strictly smaller than both its previous and next nodes
Important constraints:
- A node can only be a critical point if it has both a previous node and a next node (so the first and last nodes can never be critical points)
Given a linked list, you need to return an array [minDistance, maxDistance]
where:
minDistance
is the minimum distance between any two distinct critical pointsmaxDistance
is the maximum distance between any two distinct critical points- Distance is measured as the number of nodes between two critical points
If there are fewer than two critical points in the linked list, return [-1, -1]
.
For example, if you have a linked list with values [5, 3, 1, 2, 5, 1, 2]
:
- Node with value 1 (position 2) is a local minima (3 > 1 < 2)
- Node with value 5 (position 4) is a local maxima (2 < 5 > 1)
- Node with value 1 (position 5) is a local minima (5 > 1 < 2)
The critical points are at positions 2, 4, and 5. The minimum distance is 1 (between positions 4 and 5), and the maximum distance is 3 (between positions 2 and 5).
Intuition
To find critical points, we need to examine three consecutive nodes at a time - the previous, current, and next nodes. This is because a critical point is defined by comparing a node's value with both its neighbors.
The key insight is that we only need to track two things as we traverse the linked list:
- The position of the first critical point we encounter
- The position of the last (most recent) critical point we encounter
Why is this sufficient?
For the maximum distance: This will always be the distance between the very first and the very last critical point we find. As we traverse and find new critical points, we can update our last position, and the maximum distance is simply last - first
.
For the minimum distance: We need to find the smallest gap between any two consecutive critical points. We don't need to store all critical point positions - we just need to compare each new critical point with the previous one. Each time we find a new critical point, we calculate the distance from the last one and update our minimum if needed.
The traversal strategy is straightforward: maintain a sliding window of three nodes (a
, b
, c
representing previous, current, and next values). Check if b
is a critical point by testing:
- Is it a local minima? (
a > b < c
) - Is it a local maxima? (
a < b > c
)
We use a position counter i
to track where we are in the linked list, incrementing it as we move forward. This allows us to calculate distances between critical points without needing to store the actual nodes.
If we end up with first == last
, it means we found only one or zero critical points, so we return [-1, -1]
.
Learn more about Linked List patterns.
Solution Approach
The implementation uses a single-pass traversal approach with constant space complexity. Here's how the solution works:
Initialization:
- Set
ans = [inf, -inf]
to store the minimum and maximum distances. We initialize minimum to infinity and maximum to negative infinity for easy comparison updates. - Set
first = last = -1
to track the positions of the first and most recent critical points found. - Set
i = 0
as a position counter to track the current index in the traversal.
Main Traversal:
We traverse the linked list using a while loop with condition head.next.next
, which ensures we have at least three nodes to examine (current, next, and next's next).
For each iteration:
- Extract three consecutive values:
a = head.val
,b = head.next.val
,c = head.next.next.val
- Check if
b
(the middle node) is a critical point using the condition:a > b < c or a < b > c
a > b < c
checks for local minimaa < b > c
checks for local maxima
Critical Point Processing:
When we find a critical point at position i
:
- If it's the first critical point (
last == -1
):- Set both
first
andlast
toi
- Set both
- If it's not the first critical point:
- Update minimum distance:
ans[0] = min(ans[0], i - last)
- Update
last = i
to track the most recent critical point - Update maximum distance:
ans[1] = max(ans[1], last - first)
- Update minimum distance:
Iteration Update: After checking each position:
- Increment
i += 1
to update the position counter - Move to the next node:
head = head.next
Final Result:
- If
first == last
, it means we found at most one critical point, so return[-1, -1]
- Otherwise, return
ans
containing[minDistance, maxDistance]
The algorithm efficiently finds all critical points in a single pass with O(n) time complexity and O(1) extra space, only storing the positions of the first and last critical points rather than all critical point positions.
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 linked list: [1, 3, 2, 2, 3, 2, 2, 2, 7]
Initial Setup:
ans = [inf, -inf]
(to store min and max distances)first = -1
,last = -1
(positions of first and last critical points)i = 0
(current position counter)
Iteration 1: Position 0
- Three values:
a=1
,b=3
,c=2
- Check: Is
1 > 3 < 2
? No. Is1 < 3 > 2
? Yes! (local maxima) - Found critical point at position 1
- Since
last == -1
, this is our first critical point - Update:
first = 1
,last = 1
- Move forward:
i = 1
, advance to next node
Iteration 2: Position 1
- Three values:
a=3
,b=2
,c=2
- Check: Is
3 > 2 < 2
? No. Is3 < 2 > 2
? No. - Not a critical point
- Move forward:
i = 2
, advance to next node
Iteration 3: Position 2
- Three values:
a=2
,b=2
,c=3
- Check: Is
2 > 2 < 3
? No. Is2 < 2 > 3
? No. - Not a critical point
- Move forward:
i = 3
, advance to next node
Iteration 4: Position 3
- Three values:
a=2
,b=3
,c=2
- Check: Is
2 > 3 < 2
? No. Is2 < 3 > 2
? Yes! (local maxima) - Found critical point at position 4
- Since
last != -1
, this is not our first - Update minimum distance:
ans[0] = min(inf, 4 - 1) = 3
- Update
last = 4
- Update maximum distance:
ans[1] = max(-inf, 4 - 1) = 3
- Move forward:
i = 4
, advance to next node
Iteration 5: Position 4
- Three values:
a=3
,b=2
,c=2
- Check: Is
3 > 2 < 2
? No. Is3 < 2 > 2
? No. - Not a critical point
- Move forward:
i = 5
, advance to next node
Iteration 6: Position 5
- Three values:
a=2
,b=2
,c=2
- Check: Is
2 > 2 < 2
? No. Is2 < 2 > 2
? No. - Not a critical point
- Move forward:
i = 6
, advance to next node
Iteration 7: Position 6
- Three values:
a=2
,b=2
,c=7
- Check: Is
2 > 2 < 7
? No. Is2 < 2 > 7
? No. - Not a critical point
- Move forward:
i = 7
, advance to next node
End of traversal (can't check position 7 as we need a next node after it)
Final Result:
- We found critical points at positions 1 and 4
first = 1
,last = 4
- Since
first != last
, we have at least 2 critical points - Return
ans = [3, 3]
The minimum distance between critical points is 3, and the maximum distance is also 3 (since we only have 2 critical points).
Solution Implementation
1# Definition for singly-linked list.
2# class ListNode:
3# def __init__(self, val=0, next=None):
4# self.val = val
5# self.next = next
6
7from typing import List, Optional
8from math import inf
9
10class Solution:
11 def nodesBetweenCriticalPoints(self, head: Optional[ListNode]) -> List[int]:
12 """
13 Find the minimum and maximum distances between critical points in a linked list.
14 A critical point is a local maxima or local minima.
15
16 Args:
17 head: The head of the linked list
18
19 Returns:
20 A list containing [minimum_distance, maximum_distance] between critical points.
21 Returns [-1, -1] if there are fewer than 2 critical points.
22 """
23 # Initialize result with infinity for minimum and negative infinity for maximum
24 min_distance = inf
25 max_distance = -inf
26
27 # Track positions of first and last critical points found
28 first_critical_point = -1
29 last_critical_point = -1
30
31 # Current position in the linked list (starting from the second node)
32 current_position = 0
33
34 # Traverse the linked list checking for critical points
35 # We need at least 3 nodes to determine if the middle one is critical
36 while head.next and head.next.next:
37 # Get three consecutive node values
38 previous_val = head.val
39 current_val = head.next.val
40 next_val = head.next.next.val
41
42 # Check if current node is a critical point (local minimum or maximum)
43 is_local_minimum = previous_val > current_val < next_val
44 is_local_maximum = previous_val < current_val > next_val
45
46 if is_local_minimum or is_local_maximum:
47 if last_critical_point == -1:
48 # First critical point found
49 first_critical_point = current_position
50 last_critical_point = current_position
51 else:
52 # Update minimum distance between consecutive critical points
53 min_distance = min(min_distance, current_position - last_critical_point)
54
55 # Update the last critical point position
56 last_critical_point = current_position
57
58 # Maximum distance is always between first and last critical points
59 max_distance = max(max_distance, last_critical_point - first_critical_point)
60
61 # Move to the next position
62 current_position += 1
63 head = head.next
64
65 # Check if we found at least 2 critical points
66 if first_critical_point == last_critical_point:
67 return [-1, -1]
68 else:
69 return [min_distance, max_distance]
70
1/**
2 * Definition for singly-linked list.
3 * public class ListNode {
4 * int val;
5 * ListNode next;
6 * ListNode() {}
7 * ListNode(int val) { this.val = val; }
8 * ListNode(int val, ListNode next) { this.val = val; this.next = next; }
9 * }
10 */
11class Solution {
12 public int[] nodesBetweenCriticalPoints(ListNode head) {
13 // Initialize result array: [minDistance, maxDistance]
14 // Use a large initial value for minDistance to find minimum
15 int[] result = {Integer.MAX_VALUE, 0};
16
17 // Track the index of first and last critical points found
18 int firstCriticalIndex = -1;
19 int lastCriticalIndex = -1;
20
21 // Traverse the linked list starting from index 0
22 // We need at least 3 nodes to check for critical points
23 int currentIndex = 0;
24 while (head.next != null && head.next.next != null) {
25 // Get values of three consecutive nodes
26 int previousValue = head.val;
27 int currentValue = head.next.val;
28 int nextValue = head.next.next.val;
29
30 // Check if current node is a critical point
31 // A critical point is either a local minimum or local maximum
32 boolean isLocalMinimum = currentValue < previousValue && currentValue < nextValue;
33 boolean isLocalMaximum = currentValue > previousValue && currentValue > nextValue;
34
35 if (isLocalMinimum || isLocalMaximum) {
36 // If this is the first critical point found
37 if (lastCriticalIndex == -1) {
38 firstCriticalIndex = currentIndex;
39 lastCriticalIndex = currentIndex;
40 } else {
41 // Update minimum distance between consecutive critical points
42 result[0] = Math.min(result[0], currentIndex - lastCriticalIndex);
43
44 // Update the last critical point index
45 lastCriticalIndex = currentIndex;
46
47 // Update maximum distance (between first and last critical points)
48 result[1] = Math.max(result[1], lastCriticalIndex - firstCriticalIndex);
49 }
50 }
51
52 // Move to the next node
53 head = head.next;
54 currentIndex++;
55 }
56
57 // If less than 2 critical points were found, return [-1, -1]
58 if (firstCriticalIndex == lastCriticalIndex) {
59 return new int[] {-1, -1};
60 }
61
62 return result;
63 }
64}
65
1/**
2 * Definition for singly-linked list.
3 * struct ListNode {
4 * int val;
5 * ListNode *next;
6 * ListNode() : val(0), next(nullptr) {}
7 * ListNode(int x) : val(x), next(nullptr) {}
8 * ListNode(int x, ListNode *next) : val(x), next(next) {}
9 * };
10 */
11class Solution {
12public:
13 vector<int> nodesBetweenCriticalPoints(ListNode* head) {
14 // Initialize result vector with [max_possible_value, 0]
15 // result[0] will store minimum distance, result[1] will store maximum distance
16 vector<int> result = {INT_MAX, 0};
17
18 // Track the index of first and last critical points found
19 int firstCriticalIndex = -1;
20 int lastCriticalIndex = -1;
21
22 // Traverse the linked list with a sliding window of 3 nodes
23 // i tracks the index of the middle node in our window
24 for (int currentIndex = 0; head->next->next != nullptr; head = head->next, ++currentIndex) {
25 // Get values of three consecutive nodes
26 int previousValue = head->val;
27 int currentValue = head->next->val;
28 int nextValue = head->next->next->val;
29
30 // Check if current node is a critical point
31 // A critical point is either a local minimum or local maximum
32 bool isLocalMinimum = (currentValue < previousValue && currentValue < nextValue);
33 bool isLocalMaximum = (currentValue > previousValue && currentValue > nextValue);
34
35 if (isLocalMinimum || isLocalMaximum) {
36 if (lastCriticalIndex == -1) {
37 // This is the first critical point found
38 firstCriticalIndex = currentIndex;
39 lastCriticalIndex = currentIndex;
40 } else {
41 // Update minimum distance between consecutive critical points
42 result[0] = min(result[0], currentIndex - lastCriticalIndex);
43
44 // Update the last critical point index
45 lastCriticalIndex = currentIndex;
46
47 // Update maximum distance (between first and current critical point)
48 result[1] = max(result[1], lastCriticalIndex - firstCriticalIndex);
49 }
50 }
51 }
52
53 // If less than 2 critical points were found, return [-1, -1]
54 // This happens when firstCriticalIndex equals lastCriticalIndex (only one critical point)
55 // or when no critical points were found (both remain -1)
56 if (firstCriticalIndex == lastCriticalIndex) {
57 return vector<int>{-1, -1};
58 }
59
60 return result;
61 }
62};
63
1/**
2 * Definition for singly-linked list.
3 * class ListNode {
4 * val: number
5 * next: ListNode | null
6 * constructor(val?: number, next?: ListNode | null) {
7 * this.val = (val===undefined ? 0 : val)
8 * this.next = (next===undefined ? null : next)
9 * }
10 * }
11 */
12
13/**
14 * Finds the minimum and maximum distances between critical points in a linked list.
15 * A critical point is a local maxima or local minima (value greater or less than both neighbors).
16 * @param head - The head of the linked list
17 * @returns An array [minDistance, maxDistance] between critical points, or [-1, -1] if less than 2 critical points exist
18 */
19function nodesBetweenCriticalPoints(head: ListNode | null): number[] {
20 // Initialize result array with [minDistance, maxDistance]
21 const result: number[] = [Infinity, 0];
22
23 // Track the position of first and last critical points found
24 let firstCriticalPoint: number = -1;
25 let lastCriticalPoint: number = -1;
26
27 // Traverse the linked list, checking each node with its neighbors
28 let currentPosition: number = 0;
29 while (head !== null && head.next !== null && head.next.next !== null) {
30 // Get values of current node and its two successors
31 const previousValue: number = head.val;
32 const currentValue: number = head.next.val;
33 const nextValue: number = head.next.next.val;
34
35 // Check if current node (middle one) is a critical point
36 const isLocalMinima: boolean = currentValue < Math.min(previousValue, nextValue);
37 const isLocalMaxima: boolean = currentValue > Math.max(previousValue, nextValue);
38
39 if (isLocalMinima || isLocalMaxima) {
40 // If this is the first critical point found
41 if (lastCriticalPoint < 0) {
42 firstCriticalPoint = currentPosition;
43 lastCriticalPoint = currentPosition;
44 } else {
45 // Update minimum distance between consecutive critical points
46 result[0] = Math.min(result[0], currentPosition - lastCriticalPoint);
47 // Update the last critical point position
48 lastCriticalPoint = currentPosition;
49 // Update maximum distance (distance from first to current critical point)
50 result[1] = Math.max(result[1], lastCriticalPoint - firstCriticalPoint);
51 }
52 }
53
54 // Move to next node
55 head = head.next;
56 currentPosition++;
57 }
58
59 // If less than 2 critical points were found, return [-1, -1]
60 return firstCriticalPoint === lastCriticalPoint ? [-1, -1] : result;
61}
62
Time and Space Complexity
Time Complexity: O(n)
, where n
is the length of the linked list. The algorithm traverses the linked list exactly once using a single while loop. In each iteration, it performs constant-time operations: comparing three node values (a
, b
, c
), updating variables, and moving the pointer forward. Since we visit each node once (except the last two nodes due to the head.next.next
condition), the total time complexity is linear with respect to the number of nodes.
Space Complexity: O(1)
. The algorithm uses only a fixed amount of extra space regardless of the input size. The variables used are:
ans
: a list with exactly 2 elementsfirst
,last
,i
: integer variables for tracking positionsa
,b
,c
: temporary variables for storing node values
All these variables require constant space that doesn't grow with the size of the input linked list, resulting in constant space complexity.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Off-by-One Error in Position Tracking
One of the most common mistakes is incorrectly tracking the position of critical points. The position counter should represent the index of the middle node being checked (head.next), not the current head node.
Incorrect approach:
current_position = 0 # Starting at 0
while head.next and head.next.next:
# Check if head.next is critical
if is_critical:
# current_position represents head, not head.next!
positions.append(current_position)
current_position += 1
head = head.next
Correct approach:
current_position = 1 # Start at 1 since we're checking head.next
while head.next and head.next.next:
# Check if head.next is critical
if is_critical:
# current_position correctly represents head.next
positions.append(current_position)
current_position += 1
head = head.next
2. Incorrect Critical Point Detection Logic
A common error is using the wrong comparison operators or forgetting that both conditions must be strict inequalities.
Incorrect approaches:
# Wrong: Using >= or <= instead of strict inequalities is_critical = (a >= b <= c) or (a <= b >= c) # Wrong: Using AND instead of OR between conditions is_critical = (a > b < c) and (a < b > c) # Wrong: Checking only one type of critical point is_critical = a > b < c # Missing local maxima check
Correct approach:
is_local_minimum = previous_val > current_val < next_val is_local_maximum = previous_val < current_val > next_val is_critical = is_local_minimum or is_local_maximum
3. Incorrect Distance Calculation
The distance between critical points should be the difference in their positions, not the number of nodes between them plus one.
Incorrect:
distance = current_position - last_position + 1 # Wrong!
Correct:
distance = current_position - last_position # Correct
4. Not Handling Edge Cases Properly
Failing to check if there are fewer than two critical points before returning the result.
Incorrect approach:
# Returning uninitialized or infinity values if min_distance == inf: return [-1, -1] # This misses the case where only one critical point exists
Correct approach:
# Check if first and last are the same (meaning 0 or 1 critical points) if first_critical_point == last_critical_point: return [-1, -1]
5. Updating Maximum Distance Incorrectly
A subtle mistake is updating the maximum distance using consecutive critical points instead of the distance from first to current.
Incorrect:
max_distance = max(max_distance, current_position - last_critical_point) # Wrong!
Correct:
max_distance = max(max_distance, current_position - first_critical_point)
# Or simply: max_distance = last_critical_point - first_critical_point (after updating last)
In a binary min heap, the maximum element can be found in:
Recommended Readings
Linked List Cycle Given a linked list with potentially a loop determine whether the linked list from the first node contains a cycle in it For bonus points do this with constant space Parameters nodes The first node of a linked list with potentially a loop Result Whether there is a loop contained
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!