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
66 changes: 66 additions & 0 deletions problems/3903-smallest-stable-index-i/analysis.md
Original file line number Diff line number Diff line change
@@ -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.
84 changes: 84 additions & 0 deletions problems/3903-smallest-stable-index-i/problem.md
Original file line number Diff line number Diff line change
@@ -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`
37 changes: 37 additions & 0 deletions problems/3903-smallest-stable-index-i/solution_daily_20260904.go
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
@@ -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)
}
})
}
}