diff --git a/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/analysis.md b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/analysis.md new file mode 100644 index 0000000..0c53c48 --- /dev/null +++ b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/analysis.md @@ -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. diff --git a/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/problem.md b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/problem.md new file mode 100644 index 0000000..11ef856 --- /dev/null +++ b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/problem.md @@ -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` diff --git a/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution.go b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution.go new file mode 100644 index 0000000..81e0ae0 --- /dev/null +++ b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution.go @@ -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} +} diff --git a/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution_test.go b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution_test.go new file mode 100644 index 0000000..652432b --- /dev/null +++ b/problems/2058-find-the-minimum-and-maximum-number-of-nodes-between-critical-points/solution_test.go @@ -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) + } + }) + } +}