Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# 2058. Find the Minimum and Maximum Number of Nodes Between Critical Points

[LeetCode Link](https://leetcode.com/problems/find-the-minimum-and-maximum-number-of-nodes-between-critical-points/)

Difficulty: Medium
Topics: Linked List
Acceptance Rate: 70.5%

## Hints

### Hint 1

Being a critical point is a purely *local* property: a node qualifies based only on itself and its two immediate neighbours. That means you never need to look at the whole list at once, and you never need random access. Ask yourself what the smallest window of the list you must hold at any moment is, and whether one forward pass can carry that window along.

### Hint 2

Walk the list with three references — previous, current, next — and a running index counter, so each node knows its own position. Whenever `current` is strictly greater than both neighbours (or strictly smaller than both), you have found a critical point at that index. Now the problem stops being about linked lists at all: it becomes "given a stream of increasing indices, find the smallest and largest gap."

### Hint 3

You do not need to store the indices in a slice. For the **maximum** distance, the answer is always `lastCriticalIndex - firstCriticalIndex`, because the extremes are the farthest apart pair by definition. For the **minimum** distance, the closest pair must be *adjacent* in sorted order — any non-adjacent pair straddles an adjacent pair and is therefore at least as wide. So remember only three numbers as you go: the first critical index, the previous critical index, and the running minimum gap. That collapses the space to O(1).

## Approach

The two halves of the answer come from the same single pass, but they use different bookkeeping.

**Step 1 — detect critical points.** A node is critical only if it has both a predecessor and a successor, so the head and the tail are never candidates. Traverse with a sliding triple `(prev, cur, next)` starting at `prev = head`, `cur = head.Next`, and keep a 1-based index for `cur`. At each step:

- local maxima: `cur.Val > prev.Val && cur.Val > next.Val`
- local minima: `cur.Val < prev.Val && cur.Val < next.Val`

Note that both comparisons are *strict*. A plateau such as `[2,2,2]` produces no critical point, which is exactly why example 3 has only two critical points despite the list wobbling several times.

**Step 2 — track distances in O(1) space.** Maintain three variables:

- `first` — index of the first critical point seen (set once).
- `prev` — index of the most recently seen critical point.
- `minDist` — the smallest gap seen so far.

When a new critical point at index `i` appears: if `first` is unset, record `first = i`. Otherwise update `minDist = min(minDist, i - prev)`. In both cases set `prev = i`.

Why is looking only at adjacent gaps enough for the minimum? The critical indices are discovered in increasing order. For any pair `i < j` that are not adjacent in that order, there is some `k` with `i < k < j`, and `k - i < j - i`. So a non-adjacent pair can never be the unique closest pair — checking consecutive gaps covers every candidate. Symmetrically, the maximum is pinned by the two extremes, so after the pass `maxDist = prev - first` (where `prev` now holds the last critical index).

**Step 3 — the fewer-than-two case.** If we ended with zero or one critical point, no pair exists and the answer is `[-1, -1]`. Detect this by checking whether `minDist` was ever updated, or equivalently whether `first == prev`.

**Worked example:** `head = [5,3,1,2,5,1,2]`, 1-based indices.

| index | prev, cur, next | critical? | first | prev crit | minDist |
|-------|-----------------|-----------|-------|-----------|---------|
| 2 | 5, 3, 1 | no (3 is between) | – | – | ∞ |
| 3 | 3, 1, 2 | minima | 3 | 3 | ∞ |
| 4 | 1, 2, 5 | no | 3 | 3 | ∞ |
| 5 | 2, 5, 1 | maxima | 3 | 5 | 2 |
| 6 | 5, 1, 2 | minima | 3 | 6 | 1 |

The loop stops at index 6 because index 7 is the tail. Result: `minDist = 1`, `maxDist = 6 - 3 = 3`, i.e. `[1, 3]` — matching the expected output.

This is an approachable Medium: the detection rule is simple, and the real learning is the "closest pair must be adjacent" observation that lets you drop the array of indices entirely. The two traps that actually cost people submissions are using non-strict comparisons (breaking on plateaus) and forgetting the fewer-than-two guard.

## Complexity Analysis

Time Complexity: O(n) — one pass, visiting each node a constant number of times.
Space Complexity: O(1) — only a handful of integers and node pointers, regardless of list length.

## Edge Cases

- **Fewer than two critical points** (`[3,1]`, `[2,1,3]`) — must return `[-1, -1]`. A list with exactly one critical point is the sneaky version of this: the detection code runs and succeeds, but there is still no pair, so the guard must be on the *count*, not on whether any critical point exists.
- **Minimum-length list** (`n == 2`, guaranteed by the constraints as the floor) — no node has both neighbours, so the traversal body never executes. The code must not dereference `head.Next.Next`.
- **Plateaus / equal adjacent values** (`[1,3,2,2,3,2,2,2,7]`, `[5,5,5,5,5]`) — because both comparisons are strict, flat runs contribute nothing. Using `>=` or `<=` here silently produces wrong answers on the given examples.
- **Head and tail are never critical** — example 3 explicitly calls this out: the final `7` is larger than its predecessor but has no successor, so it does not count.
- **Strictly alternating list** (`[1,5,1,5,1,5,1]`) — nearly every interior node is critical, so `minDist` is 1 and `maxDist` spans almost the whole list. Good check that you are comparing consecutive gaps rather than only the first pair.
- **Monotonic list** (`[1,2,3,4,5]`) — zero critical points; exercises the `[-1, -1]` path from the other direction.
- **Overflow / initialisation** — initialise `minDist` to a sentinel larger than any possible gap (`n` itself, or `math.MaxInt32`) rather than `0`, otherwise the `min` update can never fire.
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
---
number: "2058"
frontend_id: "2058"
title: "Find the Minimum and Maximum Number of Nodes Between Critical Points"
slug: "find-the-minimum-and-maximum-number-of-nodes-between-critical-points"
difficulty: "Medium"
topics:
- "Linked List"
acceptance_rate: 7050.6
is_premium: false
created_at: "2026-08-31T05:52:55.268656+00:00"
fetched_at: "2026-08-31T05:52:55.268656+00:00"
link: "https://leetcode.com/problems/find-the-minimum-and-maximum-number-of-nodes-between-critical-points/"
date: "2026-08-31"
---

# 2058. Find the Minimum and Maximum Number of Nodes Between Critical Points

A **critical point** in a linked list is defined as **either** a **local maxima** or a **local minima**.

A node is a **local maxima** if the current node has a value **strictly greater** than the previous node and the next node.

A node is a **local minima** if the current node has a value **strictly smaller** than the previous node and the next node.

Note that a node can only be a local maxima/minima if there exists **both** a previous node and a next node.

Given a linked list `head`, return _an array of length 2 containing_`[minDistance, maxDistance]`_where_`minDistance` _is the**minimum distance** between **any two distinct** critical points and _`maxDistance` _is the**maximum distance** between **any two distinct** critical points. If there are **fewer** than two critical points, return _`[-1, -1]`.



**Example 1:**

![](https://assets.leetcode.com/uploads/2021/10/13/a1.png)


**Input:** head = [3,1]
**Output:** [-1,-1]
**Explanation:** There are no critical points in [3,1].


**Example 2:**

![](https://assets.leetcode.com/uploads/2021/10/13/a2.png)


**Input:** head = [5,3,1,2,5,1,2]
**Output:** [1,3]
**Explanation:** There are three critical points:
- [5,3,**_1_** ,2,5,1,2]: The third node is a local minima because 1 is less than 3 and 2.
- [5,3,1,2,_**5**_ ,1,2]: The fifth node is a local maxima because 5 is greater than 2 and 1.
- [5,3,1,2,5,_**1**_ ,2]: The sixth node is a local minima because 1 is less than 5 and 2.
The minimum distance is between the fifth and the sixth node. minDistance = 6 - 5 = 1.
The maximum distance is between the third and the sixth node. maxDistance = 6 - 3 = 3.


**Example 3:**

![](https://assets.leetcode.com/uploads/2021/10/14/a5.png)


**Input:** head = [1,3,2,2,3,2,2,2,7]
**Output:** [3,3]
**Explanation:** There are two critical points:
- [1,_**3**_ ,2,2,3,2,2,2,7]: The second node is a local maxima because 3 is greater than 1 and 2.
- [1,3,2,2,_**3**_ ,2,2,2,7]: The fifth node is a local maxima because 3 is greater than 2 and 2.
Both the minimum and maximum distances are between the second and the fifth node.
Thus, minDistance and maxDistance is 5 - 2 = 3.
Note that the last node is not considered a local maxima because it does not have a next node.




**Constraints:**

* The number of nodes in the list is in the range `[2, 105]`.
* `1 <= Node.val <= 105`
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package main

// Single pass with a sliding (prev, cur, next) window.
//
// A node is a critical point when it is strictly greater or strictly smaller
// than both of its neighbours, so detection only needs three nodes at a time.
// Once a critical point is found at index i we do not store it: the maximum
// distance is always last-first, and the minimum distance can only occur
// between two consecutive critical points (any wider pair straddles a closer
// one). Tracking first, previous and the running minimum keeps space at O(1).

// ListNode is the singly-linked list node used by LeetCode.
type ListNode struct {
Val int
Next *ListNode
}

func nodesBetweenCriticalPoints(head *ListNode) []int {
const notFound = -1

first, last := notFound, notFound
minDist := notFound

prev := head
if prev == nil {
return []int{-1, -1}
}

// cur is the candidate node; index is its 1-based position in the list.
index := 2
for cur := prev.Next; cur != nil && cur.Next != nil; cur, index = cur.Next, index+1 {
next := cur.Next

isMaxima := cur.Val > prev.Val && cur.Val > next.Val
isMinima := cur.Val < prev.Val && cur.Val < next.Val
prev = cur

if !isMaxima && !isMinima {
continue
}

if first == notFound {
first = index
} else if gap := index - last; minDist == notFound || gap < minDist {
minDist = gap
}
last = index
}

if minDist == notFound {
return []int{-1, -1}
}
return []int{minDist, last - first}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package main

import (
"reflect"
"testing"
)

// buildList turns a slice of values into a singly-linked list and returns its head.
func buildList(vals []int) *ListNode {
dummy := &ListNode{}
tail := dummy
for _, v := range vals {
tail.Next = &ListNode{Val: v}
tail = tail.Next
}
return dummy.Next
}

func TestSolution(t *testing.T) {
tests := []struct {
name string
head []int
expected []int
}{
{"example 1: no critical points in a two node list", []int{3, 1}, []int{-1, -1}},
{"example 2: three critical points", []int{5, 3, 1, 2, 5, 1, 2}, []int{1, 3}},
{"example 3: plateaus are not critical points", []int{1, 3, 2, 2, 3, 2, 2, 2, 7}, []int{3, 3}},
{"edge case: exactly one critical point", []int{2, 1, 3}, []int{-1, -1}},
{"edge case: two equal nodes", []int{1, 1}, []int{-1, -1}},
{"edge case: all values equal", []int{5, 5, 5, 5, 5}, []int{-1, -1}},
{"edge case: strictly increasing list", []int{1, 2, 3, 4, 5}, []int{-1, -1}},
{"edge case: strictly alternating list", []int{1, 5, 1, 5, 1, 5, 1}, []int{1, 4}},
{"edge case: adjacent critical points", []int{1, 2, 1, 2, 1}, []int{1, 2}},
{"edge case: closest pair is not the first pair", []int{1, 9, 1, 1, 1, 9, 8, 9, 1}, []int{1, 6}},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := nodesBetweenCriticalPoints(buildList(tt.head))
if !reflect.DeepEqual(got, tt.expected) {
t.Errorf("nodesBetweenCriticalPoints(%v) = %v, want %v", tt.head, got, tt.expected)
}
})
}
}