LeetCode 3555 - Smallest Subarray to Sort in Every Sliding Window
The problem asks us to examine every contiguous subarray (window) of length k in the array nums and determine the minimum continuous segment within that window that must be sorted to make the entire window non-decreasing.
Difficulty: 🟡 Medium
Topics: Array, Two Pointers, Stack, Greedy, Sorting, Monotonic Stack
Solution
Problem Understanding
The problem asks us to examine every contiguous subarray (window) of length k in the array nums and determine the minimum continuous segment within that window that must be sorted to make the entire window non-decreasing. In other words, for each sliding window, we want the smallest subarray that, if sorted, results in the window being sorted. If the window is already sorted, the required length is zero.
The input is an integer array nums of length n (1 <= n <= 1000) and an integer k (1 <= k <= n). Each integer in nums is positive (1 <= nums[i] <= 10^6). The output is an array of length n - k + 1, where each element corresponds to the minimum length of the subarray to sort for the respective window.
Important edge cases include windows that are already sorted (requiring length 0), windows that are fully decreasing (requiring length k), and windows containing repeated values, which can affect sorting decisions. The problem guarantees that nums is non-empty and that k is a valid window size, so no additional checks for invalid input are needed.
Approaches
Brute Force
The brute-force approach considers each window independently. For each window of length k, we can check all possible continuous subarrays, sort them, and test if the entire window becomes non-decreasing. While this approach is correct, it is highly inefficient. The number of subarrays in a window is O(k^2), and sorting each subarray takes O(k log k), so for n - k + 1 windows, the total time complexity is O((n - k + 1) * k^3 log k), which is unacceptable for n up to 1000.
Optimal Approach
The key observation is that, for each window, the smallest subarray to sort is determined by the positions where the order is violated. If we can track the indices where nums[i] > nums[i + 1] within the window, the subarray to sort is simply the segment from the first such violation to the last. A monotonic stack or a linear scan can efficiently compute this segment in O(k) per window. By sliding the window and updating this segment, we reduce unnecessary repeated computations.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((n-k+1) * k^3 log k) | O(k) | Checks all possible subarrays within each window |
| Optimal | O(n * k) | O(k) | Uses linear scan per window, finds first and last violation efficiently |
Algorithm Walkthrough
- Initialize an empty list
resultto store the minimum subarray length for each window. - Iterate over all windows
nums[i:i+k]whereiranges from0ton-k. - For each window, initialize two pointers
leftandrightto track the first and last violations of the non-decreasing order. - Perform a left-to-right scan to find the first index
leftwherenums[j] > nums[j+1]. - Perform a right-to-left scan to find the last index
rightwherenums[j-1] > nums[j]. - If no violations are found (
leftnot set), append0toresult. - Otherwise, append
right - left + 1toresult. - Return
result.
Why it works: Within any subarray, only the first and last violations determine the minimal continuous segment that disrupts non-decreasing order. Sorting this segment restores order for the entire window. By scanning left-to-right and right-to-left, we capture exactly this segment.
Python Solution
from typing import List
class Solution:
def minSubarraySort(self, nums: List[int], k: int) -> List[int]:
n = len(nums)
result = []
for i in range(n - k + 1):
window = nums[i:i+k]
left, right = -1, -1
# Find first violation from the left
for j in range(k - 1):
if window[j] > window[j + 1]:
left = j
break
# Find first violation from the right
for j in range(k - 1, 0, -1):
if window[j - 1] > window[j]:
right = j
break
if left == -1: # Window is already sorted
result.append(0)
else:
result.append(right - left + 1)
return result
Implementation Walkthrough: The code iterates over all possible windows. For each window, it identifies the first and last violations of non-decreasing order. If no violations exist, the window is already sorted. Otherwise, it computes the length of the segment to sort using the indices of the first and last violations.
Go Solution
func minSubarraySort(nums []int, k int) []int {
n := len(nums)
result := make([]int, 0, n-k+1)
for i := 0; i <= n-k; i++ {
left, right := -1, -1
window := nums[i : i+k]
for j := 0; j < k-1; j++ {
if window[j] > window[j+1] {
left = j
break
}
}
for j := k - 1; j > 0; j-- {
if window[j-1] > window[j] {
right = j
break
}
}
if left == -1 {
result = append(result, 0)
} else {
result = append(result, right-left+1)
}
}
return result
}
Go-specific notes: Slices are used instead of arrays, preallocating result to avoid repeated resizing. The logic mirrors the Python version, handling empty windows naturally by the loop limits.
Worked Examples
Example 1: nums = [1,3,2,4,5], k = 3
| Window | window scan left->right | window scan right->left | Subarray length |
|---|---|---|---|
| [1,3,2] | left=1 | right=2 | 2 |
| [3,2,4] | left=0 | right=1 | 2 |
| [2,4,5] | none | none | 0 |
Example 2: nums = [5,4,3,2,1], k = 4
| Window | window scan left->right | window scan right->left | Subarray length |
|---|---|---|---|
| [5,4,3,2] | left=0 | right=3 | 4 |
| [4,3,2,1] | left=0 | right=3 | 4 |
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n * k) | Each of the n-k+1 windows is scanned left and right, each scan is O(k) |
| Space | O(n-k+1) | Output array of length n-k+1; auxiliary storage for window is O(k) |
The algorithm avoids sorting windows, which keeps complexity manageable for n ≤ 1000.
Test Cases
# Provided examples
assert Solution().minSubarraySort([1,3,2,4,5], 3) == [2,2,0] # Example 1
assert Solution().minSubarraySort([5,4,3,2,1], 4) == [4,4] # Example 2
# Edge cases
assert Solution().minSubarraySort([1,2,3,4,5], 5) == [0] # Already sorted full array
assert Solution().minSubarraySort([1], 1) == [0] # Single element window
assert Solution().minSubarraySort([2,1], 2) == [2] # Two elements reversed
# Repeated elements
assert Solution().minSubarraySort([1,2,2,1], 3) == [2,2] # Duplicates affect segment
| Test | Why |
|---|---|
[1,3,2,4,5], k=3 |
Validates basic case with partial unsorted windows |
[5,4,3,2,1], k=4 |
Validates completely reversed windows |
[1,2,3,4,5], k=5 |
Already sorted full window |
[1], k=1 |
Minimal input size |
[2,1], k=2 |
Minimal window size with descending order |
[1,2,2,1], k=3 |
Tests repeated elements affecting subarray length |
Edge Cases
Single-element windows: When k = 1, each window is trivially sorted, so the algorithm must correctly return 0. This is handled because the left and right violation scans never find a violation, leaving left = -1.
Fully reversed windows: If a window is strictly decreasing, the subarray to sort is the entire