From 099481b8f8eceb15a458007f29661ee9dc4098c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Sat, 5 Sep 2026 04:47:40 +0000 Subject: [PATCH] feat: add solution for 3904. Smallest Stable Index II --- .../3904-smallest-stable-index-ii/analysis.md | 83 ++++++++++++++++++ .../3904-smallest-stable-index-ii/problem.md | 84 +++++++++++++++++++ .../3904-smallest-stable-index-ii/solution.go | 37 ++++++++ .../solution_test.go | 77 +++++++++++++++++ 4 files changed, 281 insertions(+) create mode 100644 problems/3904-smallest-stable-index-ii/analysis.md create mode 100644 problems/3904-smallest-stable-index-ii/problem.md create mode 100644 problems/3904-smallest-stable-index-ii/solution.go create mode 100644 problems/3904-smallest-stable-index-ii/solution_test.go diff --git a/problems/3904-smallest-stable-index-ii/analysis.md b/problems/3904-smallest-stable-index-ii/analysis.md new file mode 100644 index 0000000..0c41529 --- /dev/null +++ b/problems/3904-smallest-stable-index-ii/analysis.md @@ -0,0 +1,83 @@ +# 3904. Smallest Stable Index II + +[LeetCode Link](https://leetcode.com/problems/smallest-stable-index-ii/) + +Difficulty: Medium +Topics: Array, Prefix Sum +Acceptance Rate: 75.2% + +## Hints + +### Hint 1 + +The brute-force reading of the problem is "for each index `i`, scan left for a max and scan right for a min," which is O(n²). But notice that as `i` moves from left to right, the left window only ever *grows* and the right window only ever *shrinks*. Whenever a quantity over a growing or shrinking window is asked for at *every* index, think about precomputing it in one sweep rather than recomputing it from scratch. + +### Hint 2 + +This is the prefix/suffix aggregate pattern (the same family as prefix sums, just with `max`/`min` instead of `+`). Define two arrays: + +- `prefMax[i] = max(nums[0..i])` +- `sufMin[i] = min(nums[i..n-1])` + +Each of these can be built in a single pass, because `prefMax[i]` depends only on `prefMax[i-1]` and `nums[i]`, and `sufMin[i]` depends only on `sufMin[i+1]` and `nums[i]`. Once you have both, the instability score at `i` is just one subtraction. + +### Hint 3 + +The critical realization is that you should **not** try to binary search the answer. Both `prefMax` and `sufMin` are non-decreasing, so their difference is *not* monotonic — a stable index can be followed by an unstable one. For example, `nums = [2, 1, 3]` gives scores `1, 1, 0`, while `nums = [1, 3, 2]` gives scores `0, 1, 1`. So the predicate "index `i` is stable" has no monotone structure to exploit, and you must scan left to right and return the first index that satisfies it. + +The other half of the insight is a space trick: since you walk left to right, `prefMax` never needs to be stored as an array — a single running variable suffices. Only `sufMin` has to be materialized, because it is built in the opposite direction. + +## Approach + +The whole problem reduces to evaluating `prefMax[i] - sufMin[i] <= k` for every `i` in increasing order and returning the first `i` that passes. + +**Step 1 — build the suffix minimum array.** Walk from the right end to the left: + +``` +sufMin[n-1] = nums[n-1] +sufMin[i] = min(nums[i], sufMin[i+1]) for i from n-2 down to 0 +``` + +After this pass, `sufMin[i]` holds the smallest value in `nums[i..n-1]`. This costs O(n) time and O(n) space. + +**Step 2 — sweep left to right with a running prefix maximum.** Maintain a variable `prefMax` initialized to `nums[0]`; before testing index `i`, fold `nums[i]` into it: + +``` +prefMax = max(prefMax, nums[i]) +if prefMax - sufMin[i] <= k: + return i +``` + +The first index that satisfies the condition is by construction the smallest stable index. If the loop finishes without a hit, return `-1`. + +**Worked example** on `nums = [5, 0, 1, 4]`, `k = 3`: + +| i | nums[i] | prefMax | sufMin[i] | score | stable? | +|---|---------|---------|-----------|-------|---------| +| 0 | 5 | 5 | 0 | 5 | no | +| 1 | 0 | 5 | 0 | 5 | no | +| 2 | 1 | 5 | 1 | 4 | no | +| 3 | 4 | 5 | 4 | 1 | **yes** | + +We return `3`, matching the expected output. Note how index 3 becomes stable only at the very end — a good reminder of why the scan cannot stop early or binary search. + +For `nums = [3, 2, 1]`, `k = 1`, the scores are `2, 2, 2`; nothing is `<= 1`, so the answer is `-1`. + +Two details worth calling out. First, both endpoints are *inclusive* and the two ranges *overlap* at `i` itself, so `nums[i]` participates in both the max and the min. That guarantees the score is always `>= 0`, since `prefMax[i] >= nums[i] >= sufMin[i]`. Second, this means `i = n-1` has score `max(nums) - nums[n-1]` and `i = 0` has score `nums[0] - min(nums)` — no index is special-cased. + +This problem is genuinely on the easier side for a Medium (hence the ~75% acceptance rate), but it is a clean, honest exercise in the prefix/suffix precomputation pattern, and the "don't binary search this" trap is a real one worth internalizing. + +## Complexity Analysis + +Time Complexity: O(n) — one right-to-left pass to build `sufMin`, plus at most one left-to-right pass. +Space Complexity: O(n) — for the `sufMin` array. The prefix maximum needs only O(1) extra space because it is consumed in the same direction it is produced. + +## Edge Cases + +- **Single element (`n == 1`).** The score is `nums[0] - nums[0] = 0`, which is `<= k` for any `k >= 0`, so the answer is always `0`. The suffix-min base case must be initialized from `nums[n-1]` rather than from a sentinel to make this fall out naturally. +- **No stable index at all.** A strictly decreasing array such as `[3, 2, 1]` with small `k` yields a constant score of `max - min` at every index. The function must return `-1` rather than defaulting to `0` or `n-1`. +- **`k = 0`.** Requires an exact match `prefMax[i] == sufMin[i]`. A non-decreasing array like `[1, 2, 3, 4]` satisfies this at every index (answer `0`), whereas `[2, 1]` satisfies it nowhere. +- **All elements equal.** Every score is `0`, so the answer is `0` for any valid `k`. A useful sanity check that the two windows overlapping at `i` is handled correctly. +- **Large values.** With `nums[i]` up to `10^9`, the difference fits comfortably in `int32`, but Go's `int` is 64-bit on all target platforms, so no overflow concern arises. The subtraction never goes negative, as argued above. +- **Answer only at the last index.** As in Example 1, a large leading element can keep every early index unstable. Make sure the loop actually runs through `i = n-1` and does not stop one short. +- **Non-monotone stability.** Inputs like `[1, 3, 2]` (scores `0, 1, 1`) prove a stable index can be followed by an unstable one; do not try to short-circuit the scan based on an assumed trend. diff --git a/problems/3904-smallest-stable-index-ii/problem.md b/problems/3904-smallest-stable-index-ii/problem.md new file mode 100644 index 0000000..c11c3e9 --- /dev/null +++ b/problems/3904-smallest-stable-index-ii/problem.md @@ -0,0 +1,84 @@ +--- +number: "3904" +frontend_id: "3904" +title: "Smallest Stable Index II" +slug: "smallest-stable-index-ii" +difficulty: "Medium" +topics: + - "Array" + - "Prefix Sum" +acceptance_rate: 7517.7 +is_premium: false +created_at: "2026-09-05T04:45:47.067127+00:00" +fetched_at: "2026-09-05T04:45:47.067127+00:00" +link: "https://leetcode.com/problems/smallest-stable-index-ii/" +date: "2026-09-05" +--- + +# 3904. Smallest Stable Index II + +You are given an integer array `nums` of length `n` and an integer `k`. + +For each index `i`, define its **instability score** as `max(nums[0..i]) - min(nums[i..n - 1])`. + +In other words: + + * `max(nums[0..i])` is the **largest** value among the elements from index 0 to index `i`. + * `min(nums[i..n - 1])` is the **smallest** value among the elements from index `i` to index `n - 1`. + + + +An index `i` is called **stable** if its instability score is **less than or equal to** `k`. + +Return the **smallest** stable index. If no such index exists, return -1. + + + +**Example 1:** + +**Input:** nums = [5,0,1,4], k = 3 + +**Output:** 3 + +**Explanation:** + + * At index 0: The maximum in `[5]` is 5, and the minimum in `[5, 0, 1, 4]` is 0, so the instability score is `5 - 0 = 5`. + * At index 1: The maximum in `[5, 0]` is 5, and the minimum in `[0, 1, 4]` is 0, so the instability score is `5 - 0 = 5`. + * At index 2: The maximum in `[5, 0, 1]` is 5, and the minimum in `[1, 4]` is 1, so the instability score is `5 - 1 = 4`. + * At index 3: The maximum in `[5, 0, 1, 4]` is 5, and the minimum in `[4]` is 4, so the instability score is `5 - 4 = 1`. + * This is the first index with an instability score less than or equal to `k = 3`. Thus, the answer is 3. + + + +**Example 2:** + +**Input:** nums = [3,2,1], k = 1 + +**Output:** -1 + +**Explanation:** + + * At index 0, the instability score is `3 - 1 = 2`. + * At index 1, the instability score is `3 - 1 = 2`. + * At index 2, the instability score is `3 - 1 = 2`. + * None of these values is less than or equal to `k = 1`, so the answer is -1. + + + +**Example 3:** + +**Input:** nums = [0], k = 0 + +**Output:** 0 + +**Explanation:** + +At index 0, the instability score is `0 - 0 = 0`, which is less than or equal to `k = 0`. Therefore, the answer is 0. + + + +**Constraints:** + + * `1 <= nums.length <= 105` + * `0 <= nums[i] <= 109` + * `0 <= k <= 109` diff --git a/problems/3904-smallest-stable-index-ii/solution.go b/problems/3904-smallest-stable-index-ii/solution.go new file mode 100644 index 0000000..358efcf --- /dev/null +++ b/problems/3904-smallest-stable-index-ii/solution.go @@ -0,0 +1,37 @@ +package main + +// 3904. Smallest Stable Index II +// +// The instability score at index i is max(nums[0..i]) - min(nums[i..n-1]). +// Both windows change by one element per step, so instead of recomputing them +// we precompute a suffix-minimum array in one right-to-left pass, then sweep +// left to right carrying a running prefix maximum and return the first index +// whose score is <= k. +// +// Note that prefMax and sufMin are both non-decreasing, so their difference is +// not monotonic (e.g. [1,3,2] gives scores 0,1,1) and binary search does not +// apply -- the linear scan is required. +// +// Time: O(n), Space: O(n). +func smallestStableIndex(nums []int, k int) int { + n := len(nums) + if n == 0 { + return -1 + } + + // sufMin[i] = min(nums[i..n-1]) + sufMin := make([]int, n) + sufMin[n-1] = nums[n-1] + for i := n - 2; i >= 0; i-- { + sufMin[i] = min(nums[i], sufMin[i+1]) + } + + prefMax := nums[0] + for i := 0; i < n; i++ { + prefMax = max(prefMax, nums[i]) + if prefMax-sufMin[i] <= k { + return i + } + } + return -1 +} diff --git a/problems/3904-smallest-stable-index-ii/solution_test.go b/problems/3904-smallest-stable-index-ii/solution_test.go new file mode 100644 index 0000000..cc5045f --- /dev/null +++ b/problems/3904-smallest-stable-index-ii/solution_test.go @@ -0,0 +1,77 @@ +package main + +import "testing" + +func TestSmallestStableIndex(t *testing.T) { + tests := []struct { + name string + nums []int + k int + expected int + }{ + {"example 1: answer only at the last index", []int{5, 0, 1, 4}, 3, 3}, + {"example 2: strictly decreasing, no stable index", []int{3, 2, 1}, 1, -1}, + {"example 3: single element with k = 0", []int{0}, 0, 0}, + {"edge case: single large element", []int{1000000000}, 0, 0}, + {"edge case: all elements equal", []int{7, 7, 7}, 0, 0}, + {"edge case: non-decreasing array is stable everywhere", []int{1, 2, 3, 4}, 0, 0}, + {"edge case: k = 0 never satisfied", []int{2, 1}, 0, -1}, + {"edge case: k large enough for index 0", []int{10, 1, 5}, 100, 0}, + {"edge case: stable index followed by unstable ones", []int{1, 3, 2}, 0, 0}, + {"edge case: valley shape stable at index 0", []int{2, 1, 3}, 1, 0}, + {"edge case: max value range just below k", []int{1000000000, 0}, 999999999, -1}, + {"edge case: max value range exactly k", []int{1000000000, 0}, 1000000000, 0}, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got := smallestStableIndex(tt.nums, tt.k) + if got != tt.expected { + t.Errorf("smallestStableIndex(%v, %d) = %v, want %v", tt.nums, tt.k, got, tt.expected) + } + }) + } +} + +// bruteForceSmallestStableIndex is the O(n^2) definition-following reference +// implementation, used to cross-check the optimized solution. +func bruteForceSmallestStableIndex(nums []int, k int) int { + for i := range nums { + maxLeft := nums[0] + for j := 0; j <= i; j++ { + maxLeft = max(maxLeft, nums[j]) + } + minRight := nums[i] + for j := i; j < len(nums); j++ { + minRight = min(minRight, nums[j]) + } + if maxLeft-minRight <= k { + return i + } + } + return -1 +} + +func TestSmallestStableIndexAgainstBruteForce(t *testing.T) { + inputs := [][]int{ + {5, 0, 1, 4}, + {3, 2, 1}, + {0}, + {1, 3, 2}, + {2, 1, 3}, + {4, 4, 0, 9, 1, 9}, + {9, 8, 7, 6, 5, 4, 3, 2, 1, 0}, + {0, 1, 2, 3, 4, 5}, + {6, 2, 6, 2, 6, 2}, + } + + for _, nums := range inputs { + for k := 0; k <= 10; k++ { + want := bruteForceSmallestStableIndex(nums, k) + got := smallestStableIndex(nums, k) + if got != want { + t.Errorf("smallestStableIndex(%v, %d) = %d, want %d", nums, k, got, want) + } + } + } +}