diff --git a/problems/3903-smallest-stable-index-i/analysis.md b/problems/3903-smallest-stable-index-i/analysis.md new file mode 100644 index 0000000..6adb6e3 --- /dev/null +++ b/problems/3903-smallest-stable-index-i/analysis.md @@ -0,0 +1,66 @@ +# 3903. Smallest Stable Index I + +[LeetCode Link](https://leetcode.com/problems/smallest-stable-index-i/) + +Difficulty: Easy +Topics: Array, Prefix Sum +Acceptance Rate: 72.5% + +## Hints + +### Hint 1 + +The score at index `i` asks two questions that point in opposite directions: something about everything *at or before* `i`, and something about everything *at or after* `i`. Whenever a per-index answer decomposes into "a fact about the prefix" and "a fact about the suffix", think about precomputing both sides once instead of recomputing them inside a loop. + +### Hint 2 + +The constraints are tiny (`n <= 100`), so a brute-force double loop passes. But the interesting version is the linear one: the running maximum of `nums[0..i]` can be maintained with a single left-to-right sweep, since `max(nums[0..i]) = max(max(nums[0..i-1]), nums[i])`. Ask yourself what the mirror-image sweep gives you for `min(nums[i..n-1])`. + +### Hint 3 + +Precompute a suffix-minimum array in one right-to-left pass: `suf[i] = min(nums[i], suf[i+1])`, with `suf[n-1] = nums[n-1]`. Then sweep left to right maintaining the running prefix maximum, and at each `i` the instability score is just `runningMax - suf[i]`. Return the first `i` where that is `<= k`. + +One trap worth naming: both `prefMax` and `sufMin` are non-decreasing in `i`, so their difference is **not** monotonic — it can go down and back up. That kills any temptation to binary search for the answer; you genuinely need to scan from the left and stop at the first hit. + +## Approach + +Note that `max(nums[0..i])` and `min(nums[i..n-1])` are both *inclusive* of index `i`, so every index has a well-defined score and the two ranges overlap at exactly one element. In particular `max(nums[0..i]) >= nums[i] >= min(nums[i..n-1])`, so the instability score is always non-negative. + +The naive approach recomputes both extremes for each `i`, costing O(n) per index and O(n²) overall. That is fine for `n <= 100`, but the decomposition into prefix/suffix aggregates gives an O(n) algorithm with almost no extra code. + +The algorithm: + +1. Handle the trivial empty input by returning `-1` (the constraints guarantee `n >= 1`, but a defensive guard keeps the slice indexing below safe). +2. Build `sufMin` of length `n` in a right-to-left pass: `sufMin[n-1] = nums[n-1]`, and for `i` from `n-2` down to `0`, `sufMin[i] = min(nums[i], sufMin[i+1])`. After this pass, `sufMin[i]` is exactly `min(nums[i..n-1])`. +3. Sweep left to right with a running `prefMax`, initialized so that the first update sets it to `nums[0]`. At index `i`, first fold `nums[i]` into `prefMax` — now `prefMax == max(nums[0..i])` — then test `prefMax - sufMin[i] <= k`. +4. Return the first `i` that passes. If the loop finishes with no hit, return `-1`. + +Because we scan indices in increasing order and return immediately, the first success is by construction the *smallest* stable index. + +Walking through Example 1, `nums = [5,0,1,4]`, `k = 3`: + +| i | nums[i] | prefMax | sufMin[i] | score | <= 3? | +|---|---------|---------|-----------|-------|-------| +| 0 | 5 | 5 | 0 | 5 | no | +| 1 | 0 | 5 | 0 | 5 | no | +| 2 | 1 | 5 | 1 | 4 | no | +| 3 | 4 | 5 | 4 | 1 | yes | + +The scan stops at `i = 3` and returns 3, matching the expected output. Notice the score sequence `5, 5, 4, 1` is decreasing here, but that is a property of this input, not of the problem — for `nums = [0, 5, 0]` the scores are `0, 5, 5`, which is why the left-to-right scan (rather than any search) is the right tool. + +The prefix maximum is folded in *before* the comparison rather than after, which is the one place an off-by-one can sneak in: forget it and index 0 would be tested against an uninitialized maximum. + +## Complexity Analysis + +Time Complexity: O(n) — one right-to-left pass to build the suffix minima, and at most one left-to-right pass to find the answer. +Space Complexity: O(n) — the `sufMin` array. This can be reduced to O(1) auxiliary space only by giving up the single-pass structure (e.g. recomputing suffix minima per index, back to O(n²) time), so O(n) space is the right trade here. + +## Edge Cases + +- **Single element (`n == 1`)**: `max(nums[0..0]) == min(nums[0..0]) == nums[0]`, so the score is 0. Since `k >= 0`, the answer is always 0. Example 3 is exactly this case. +- **No stable index at all**: as in Example 2, the loop must fall through and return `-1` rather than returning a sentinel like `n` or leaving a zero value. +- **Index 0 and index `n-1` are not special-cased**: at `i = 0` the score is `nums[0] - min(nums)`, and at `i = n-1` it is `max(nums) - nums[n-1]`. Both fall out of the general formula, so no separate branches are needed — but the `sufMin` construction must seed `sufMin[n-1] = nums[n-1]` rather than starting from a "+infinity" placed at index `n-1`. +- **All elements equal**: every score is 0, so the answer is 0 for any `k`. A good sanity check that the ranges are inclusive on both sides. +- **`k == 0`**: only indices where the prefix max equals the suffix min qualify — i.e. `nums` is non-increasing up to `i` and non-decreasing after it, in the sense that `nums[i]` is simultaneously the largest so far and the smallest remaining. Strict `<=` matters; using `<` would break Example 3. +- **Large values**: `nums[i]` and `k` go up to 10⁹. The difference stays well inside Go's `int` (64-bit on all supported platforms, and even 32-bit `int` would hold 10⁹), so no overflow handling is needed — but note the score is a difference of two values, not a sum, which is what keeps it safe. +- **Defensive empty slice**: the constraints forbid it, but returning `-1` up front avoids an out-of-range panic on `nums[n-1]` if the function is ever reused outside the judge. diff --git a/problems/3903-smallest-stable-index-i/problem.md b/problems/3903-smallest-stable-index-i/problem.md new file mode 100644 index 0000000..aa5eb46 --- /dev/null +++ b/problems/3903-smallest-stable-index-i/problem.md @@ -0,0 +1,84 @@ +--- +number: "3903" +frontend_id: "3903" +title: "Smallest Stable Index I" +slug: "smallest-stable-index-i" +difficulty: "Easy" +topics: + - "Array" + - "Prefix Sum" +acceptance_rate: 7248.5 +is_premium: false +created_at: "2026-09-04T04:52:54.680315+00:00" +fetched_at: "2026-09-04T04:52:54.680315+00:00" +link: "https://leetcode.com/problems/smallest-stable-index-i/" +date: "2026-09-04" +--- + +# 3903. Smallest Stable Index I + +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 <= 100` + * `0 <= nums[i] <= 109` + * `0 <= k <= 109` diff --git a/problems/3903-smallest-stable-index-i/solution_daily_20260904.go b/problems/3903-smallest-stable-index-i/solution_daily_20260904.go new file mode 100644 index 0000000..1d3f694 --- /dev/null +++ b/problems/3903-smallest-stable-index-i/solution_daily_20260904.go @@ -0,0 +1,37 @@ +package main + +// 3903. Smallest Stable Index I +// +// The instability score at index i splits into a prefix fact and a suffix fact: +// max(nums[0..i]) and min(nums[i..n-1]), both inclusive of i. Precompute the +// suffix minima 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. +// +// The score is not monotonic in i (both prefMax and sufMin are non-decreasing, +// so their difference can rise and fall), which is why we scan rather than +// binary search. +// +// 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++ { + // Fold nums[i] in first, so prefMax == max(nums[0..i]) at the test. + prefMax = max(prefMax, nums[i]) + if prefMax-sufMin[i] <= k { + return i + } + } + return -1 +} diff --git a/problems/3903-smallest-stable-index-i/solution_daily_20260904_test.go b/problems/3903-smallest-stable-index-i/solution_daily_20260904_test.go new file mode 100644 index 0000000..d5cf3d7 --- /dev/null +++ b/problems/3903-smallest-stable-index-i/solution_daily_20260904_test.go @@ -0,0 +1,87 @@ +package main + +import "testing" + +func TestSolution(t *testing.T) { + tests := []struct { + name string + nums []int + k int + expected int + }{ + { + name: "example 1: first stable index is the last one", + nums: []int{5, 0, 1, 4}, + k: 3, + expected: 3, + }, + { + name: "example 2: no index is stable", + nums: []int{3, 2, 1}, + k: 1, + expected: -1, + }, + { + name: "example 3: single element always scores zero", + nums: []int{0}, + k: 0, + expected: 0, + }, + { + name: "edge case: all elements equal, score is zero everywhere", + nums: []int{7, 7, 7}, + k: 0, + expected: 0, + }, + { + name: "edge case: strictly increasing array is stable at index 0", + nums: []int{1, 2, 3, 4}, + k: 0, + expected: 0, + }, + { + name: "edge case: score dips then rises, so no early stop or binary search", + nums: []int{5, 0, 2, 9, 1}, + k: 4, + expected: 2, + }, + { + name: "edge case: only the final index qualifies when k is zero", + nums: []int{1, 0, 2}, + k: 0, + expected: 2, + }, + { + name: "edge case: two decreasing elements with k too small", + nums: []int{2, 1}, + k: 0, + expected: -1, + }, + { + name: "edge case: maximum constraint values, k exactly on the boundary", + nums: []int{1000000000, 0}, + k: 1000000000, + expected: 0, + }, + { + name: "edge case: maximum constraint values, k one below the boundary", + nums: []int{1000000000, 0}, + k: 999999999, + expected: -1, + }, + { + name: "edge case: single large element with k zero", + nums: []int{1000000000}, + k: 0, + expected: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := smallestStableIndex(tt.nums, tt.k); got != tt.expected { + t.Errorf("smallestStableIndex(%v, %d) = %d, want %d", tt.nums, tt.k, got, tt.expected) + } + }) + } +}