Facebook Pixel

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 points
  • maxDistance 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).

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. The position of the first critical point we encounter
  2. 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:

  1. Extract three consecutive values: a = head.val, b = head.next.val, c = head.next.next.val
  2. 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 minima
    • a < 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 and last to i
  • 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)

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 Evaluator

Example 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. Is 1 < 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. Is 3 < 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. Is 2 < 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. Is 2 < 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. Is 3 < 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. Is 2 < 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. Is 2 < 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 elements
  • first, last, i: integer variables for tracking positions
  • a, 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)
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

In a binary min heap, the maximum element can be found in:


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More