LeetCode 3903 - Smallest Stable Index I
The problem asks us to find the smallest index i in an integer array nums such that the instability score at that index is less than or equal to a given integer k. The instability score is defined as the difference between the maximum of the prefix nums[0..
Difficulty: 🟢 Easy
Topics: Array, Prefix Sum
Solution
Problem Understanding
The problem asks us to find the smallest index i in an integer array nums such that the instability score at that index is less than or equal to a given integer k. The instability score is defined as the difference between the maximum of the prefix nums[0..i] and the minimum of the suffix nums[i..n-1]. In other words, for every index i, we compute the largest number seen so far up to i, subtract the smallest number from i to the end of the array, and check if that value is less than or equal to k.
The input nums can have up to 100 elements, each up to 10^9, and k can also be up to 10^9. This guarantees that integer arithmetic will not overflow in most languages like Python, but care is needed in languages with fixed-size integers. The expected output is a single integer representing the first stable index, or -1 if none exist.
Important edge cases include arrays of length 1, arrays where all elements are equal, arrays that are strictly increasing or decreasing, and arrays where no index meets the stability condition.
Approaches
Brute Force Approach
The brute force method is straightforward. For each index i, we compute the maximum of the prefix nums[0..i] and the minimum of the suffix nums[i..n-1], then compute the instability score and check if it is less than or equal to k. If so, we return i. Otherwise, we continue until the end of the array.
This approach is correct because it directly follows the problem definition. However, it is inefficient because for each index we are recomputing maximums and minimums repeatedly, leading to a time complexity of O(n^2). Given the constraints n <= 100, this is feasible but not optimal.
Optimal Approach
The key observation is that we can precompute the prefix maximums and suffix minimums in two linear passes. First, we create an array prefix_max where prefix_max[i] = max(nums[0..i]). Next, we create an array suffix_min where suffix_min[i] = min(nums[i..n-1]). With these two arrays, computing the instability score at any index i is a constant-time operation. This reduces the overall time complexity to O(n), which is optimal for the input size.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n^2) | O(1) | Computes max and min for each index independently |
| Optimal | O(n) | O(n) | Precomputes prefix max and suffix min for constant-time instability checks |
Algorithm Walkthrough
-
Initialize two arrays:
prefix_maxof lengthnandsuffix_minof lengthn. -
Compute
prefix_maxby iterating from left to right, settingprefix_max[i] = max(prefix_max[i-1], nums[i]). -
Compute
suffix_minby iterating from right to left, settingsuffix_min[i] = min(suffix_min[i+1], nums[i]). -
Iterate over each index
ifrom 0 ton-1: -
Compute the instability score as
prefix_max[i] - suffix_min[i]. -
If the score is less than or equal to
k, returni. -
If no index satisfies the condition, return -1.
Why it works: By precomputing prefix maximums and suffix minimums, we ensure that the calculation of instability scores is exact and follows the definition in the problem. The algorithm preserves the original order and checks each index systematically, guaranteeing the smallest stable index is returned first.
Python Solution
class Solution:
def firstStableIndex(self, nums: list[int], k: int) -> int:
n = len(nums)
prefix_max = [0] * n
suffix_min = [0] * n
# Compute prefix maximums
prefix_max[0] = nums[0]
for i in range(1, n):
prefix_max[i] = max(prefix_max[i-1], nums[i])
# Compute suffix minimums
suffix_min[-1] = nums[-1]
for i in range(n-2, -1, -1):
suffix_min[i] = min(suffix_min[i+1], nums[i])
# Find the first stable index
for i in range(n):
if prefix_max[i] - suffix_min[i] <= k:
return i
return -1
The Python code first builds two helper arrays to store the running maximum from the left and the running minimum from the right. This allows checking each index in constant time. Iterating through the array and computing prefix_max[i] - suffix_min[i] ensures correctness according to the problem statement.
Go Solution
func firstStableIndex(nums []int, k int) int {
n := len(nums)
prefixMax := make([]int, n)
suffixMin := make([]int, n)
// Compute prefix maximums
prefixMax[0] = nums[0]
for i := 1; i < n; i++ {
if nums[i] > prefixMax[i-1] {
prefixMax[i] = nums[i]
} else {
prefixMax[i] = prefixMax[i-1]
}
}
// Compute suffix minimums
suffixMin[n-1] = nums[n-1]
for i := n-2; i >= 0; i-- {
if nums[i] < suffixMin[i+1] {
suffixMin[i] = nums[i]
} else {
suffixMin[i] = suffixMin[i+1]
}
}
// Find the first stable index
for i := 0; i < n; i++ {
if prefixMax[i]-suffixMin[i] <= k {
return i
}
}
return -1
}
In Go, we handle slices instead of arrays and manually check maximum and minimum conditions due to the lack of built-in max and min functions for integers. Otherwise, the approach mirrors the Python solution.
Worked Examples
Example 1: nums = [5,0,1,4], k = 3
| i | prefix_max[i] | suffix_min[i] | instability | stable? |
|---|---|---|---|---|
| 0 | 5 | 0 | 5 | No |
| 1 | 5 | 0 | 5 | No |
| 2 | 5 | 1 | 4 | No |
| 3 | 5 | 4 | 1 | Yes |
Return 3.
Example 2: nums = [3,2,1], k = 1
| i | prefix_max[i] | suffix_min[i] | instability | stable? |
|---|---|---|---|---|
| 0 | 3 | 1 | 2 | No |
| 1 | 3 | 1 | 2 | No |
| 2 | 3 | 1 | 2 | No |
Return -1.
Example 3: nums = [0], k = 0
| i | prefix_max[i] | suffix_min[i] | instability | stable? |
|---|---|---|---|---|
| 0 | 0 | 0 | 0 | Yes |
Return 0.
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Two linear passes to build prefix_max and suffix_min, plus one linear pass to check instability |
| Space | O(n) | Two arrays of size n are used for prefix_max and suffix_min |
The complexity is linear in both time and space, which is efficient and well within the problem constraints.
Test Cases
# Provided examples
assert Solution().firstStableIndex([5,0,1,4], 3) == 3 # standard case
assert Solution().firstStableIndex([3,2,1], 1) == -1 # no stable index
assert Solution().firstStableIndex([0], 0) == 0 # single element
# Additional cases
assert Solution().firstStableIndex([1,1,1,1], 0) == 0 # all equal elements
assert Solution().firstStableIndex([1,2,3,4,5], 4) == 0 # increasing sequence
assert Solution().firstStableIndex([5,4,3,2,1], 0) == 4 # decreasing sequence
assert Solution().firstStableIndex([2,2,3,1,4], 2) == 2 # random elements
assert Solution().firstStableIndex([1000000000], 0) == 0 # large single element
| Test | Why |
|---|---|
[5,0,1,4], 3 |
Verifies example with first stable index not at 0 |
[3,2,1], 1 |
Verifies no index is stable |
[0], 0 |
Tests minimal input |
| `[ |