3555. Smallest Subarray to Sort in Every Sliding Window 🔒
Problem Description
You are given an integer array nums
and an integer k
.
The problem asks you to process sliding windows of size k
across the array. For each window (contiguous subarray of length k
), you need to find the minimum length of a continuous segment within that window that needs to be sorted to make the entire window non-decreasing.
Here's what you need to determine for each window:
- If the window is already sorted in non-decreasing order, the answer is
0
- Otherwise, find the shortest continuous portion that, when sorted, would make the entire window non-decreasing
The output should be an array of length n - k + 1
, where each element represents the answer for the corresponding window starting at that position.
For example, if you have a window [3, 1, 2, 4]
:
- The window is not sorted (not non-decreasing)
- The segment
[3, 1, 2]
needs to be sorted to get[1, 2, 3, 4]
- So the minimum length would be
3
The key insight is that for each window, you need to identify the leftmost and rightmost positions where elements are out of order, and the segment between them (inclusive) is what needs to be sorted.
Intuition
To understand which segment needs sorting, let's think about what makes an element "out of place" in a sorted array.
When we traverse an array from left to right, we keep track of the maximum value seen so far. If we encounter an element smaller than this maximum, it means this element is out of position - it should have appeared earlier in a sorted sequence. This gives us the rightmost boundary of our segment that needs sorting.
Similarly, when traversing from right to left, we track the minimum value seen so far. If we encounter an element larger than this minimum, it's also out of position - it should have appeared later in a sorted sequence. This gives us the leftmost boundary of our segment.
The key insight is that any element between these two boundaries (inclusive) needs to be part of the sorted segment. Why? Because:
- The leftmost out-of-place element found from the right-to-left scan needs to move right
- The rightmost out-of-place element found from the left-to-right scan needs to move left
- All elements between them must be rearranged to accommodate these movements
For each window of size k
, we apply this two-pointer technique:
- Start with
l = -1
andr = -1
(indicating no segment needs sorting) - Scan left to right: if
nums[j] < max_so_far
, updater = j
- Scan right to left: if
nums[j] > min_so_far
, updatel = j
- If neither
l
norr
was updated, the window is already sorted (return0
) - Otherwise, return
r - l + 1
as the length of the segment to sort
This approach efficiently identifies the minimal segment because it finds the exact boundaries where the non-decreasing property is violated from both directions.
Learn more about Stack, Greedy, Two Pointers, Sorting and Monotonic Stack patterns.
Solution Approach
The solution implements the intuition we discussed by creating a helper function f(i, j)
that processes each window from index i
to j
(inclusive).
Here's how the implementation works:
Helper Function f(i, j)
:
-
Initialize tracking variables:
mi = inf
andmx = -inf
to track minimum and maximum valuesl = -1
andr = -1
to mark the left and right boundaries of the segment to sort
-
Single loop with bidirectional processing: The function uses a clever technique to scan from both directions in a single loop:
- For position
k
fromi
toj
:- Left-to-right scan: Check position
k
- If
mx > nums[k]
, this element is smaller than the max seen so far, so updater = k
- Otherwise, update
mx = nums[k]
as the new maximum
- If
- Right-to-left scan: Check position
p = j - k + i
(mirror position from the right)- If
mi < nums[p]
, this element is larger than the min seen so far, so updatel = p
- Otherwise, update
mi = nums[p]
as the new minimum
- If
- Left-to-right scan: Check position
- For position
-
Return the result:
- If
r == -1
, no updates were made, meaning the window is already sorted, return0
- Otherwise, return
r - l + 1
as the length of the segment that needs sorting
- If
Main Function:
The main function simply applies the helper function to each possible window:
return [f(i, i + k - 1) for i in range(n - k + 1)]
This generates a list where each element represents the minimum sorting length for the window starting at index i
with length k
.
Time Complexity: O((n - k + 1) * k)
= O(n * k)
since we process each of the n - k + 1
windows, and for each window, we scan k
elements.
Space Complexity: O(1)
extra space (not counting the output array), as we only use a constant number of variables in the helper function.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 4, 3, 2, 5]
and k = 3
.
We need to process 3 windows of size 3: [1,4,3]
, [4,3,2]
, and [3,2,5]
.
Window 1: [1, 4, 3] (indices 0 to 2)
Initialize: mi = inf
, mx = -inf
, l = -1
, r = -1
Loop iteration (k goes from 0 to 2):
-
k=0:
- Left scan at index 0:
nums[0]=1
. Sincemx=-inf < 1
, updatemx=1
- Right scan at index 2 (p=2-0+0=2):
nums[2]=3
. Sincemi=inf > 3
, updatemi=3
- Left scan at index 0:
-
k=1:
- Left scan at index 1:
nums[1]=4
. Sincemx=1 < 4
, updatemx=4
- Right scan at index 1 (p=2-1+0=1):
nums[1]=4
. Sincemi=3 < 4
, found out-of-place element, setl=1
- Left scan at index 1:
-
k=2:
- Left scan at index 2:
nums[2]=3
. Sincemx=4 > 3
, found out-of-place element, setr=2
- Right scan at index 0 (p=2-2+0=0):
nums[0]=1
. Sincemi=3 > 1
, updatemi=1
- Left scan at index 2:
Result: r=2
, l=1
, so length = 2-1+1 = 2
(need to sort [4,3]
to get [1,3,4]
)
Window 2: [4, 3, 2] (indices 1 to 3)
Initialize: mi = inf
, mx = -inf
, l = -1
, r = -1
Loop iteration:
-
k=0:
- Left scan at index 1:
nums[1]=4
. Sincemx=-inf < 4
, updatemx=4
- Right scan at index 3 (p=3-0+1=3):
nums[3]=2
. Sincemi=inf > 2
, updatemi=2
- Left scan at index 1:
-
k=1:
- Left scan at index 2:
nums[2]=3
. Sincemx=4 > 3
, found out-of-place element, setr=2
- Right scan at index 2 (p=3-1+1=2):
nums[2]=3
. Sincemi=2 < 3
, found out-of-place element, setl=2
- Left scan at index 2:
-
k=2:
- Left scan at index 3:
nums[3]=2
. Sincemx=4 > 2
, found out-of-place element, setr=3
- Right scan at index 1 (p=3-2+1=1):
nums[1]=4
. Sincemi=2 < 4
, found out-of-place element, setl=1
- Left scan at index 3:
Result: r=3
, l=1
, so length = 3-1+1 = 3
(need to sort entire window [4,3,2]
to get [2,3,4]
)
Window 3: [3, 2, 5] (indices 2 to 4)
Initialize: mi = inf
, mx = -inf
, l = -1
, r = -1
Loop iteration:
-
k=0:
- Left scan at index 2:
nums[2]=3
. Sincemx=-inf < 3
, updatemx=3
- Right scan at index 4 (p=4-0+2=4):
nums[4]=5
. Sincemi=inf > 5
, updatemi=5
- Left scan at index 2:
-
k=1:
- Left scan at index 3:
nums[3]=2
. Sincemx=3 > 2
, found out-of-place element, setr=3
- Right scan at index 3 (p=4-1+2=3):
nums[3]=2
. Sincemi=5 > 2
, updatemi=2
- Left scan at index 3:
-
k=2:
- Left scan at index 4:
nums[4]=5
. Sincemx=3 < 5
, updatemx=5
- Right scan at index 2 (p=4-2+2=2):
nums[2]=3
. Sincemi=2 < 3
, found out-of-place element, setl=2
- Left scan at index 4:
Result: r=3
, l=2
, so length = 3-2+1 = 2
(need to sort [3,2]
to get [2,3,5]
)
Final Output: [2, 3, 2]
Solution Implementation
1class Solution:
2 def minSubarraySort(self, nums: List[int], k: int) -> List[int]:
3 def find_unsorted_length(start: int, end: int) -> int:
4 """
5 Find the minimum length of unsorted subarray within nums[start:end+1].
6
7 Uses a two-pointer approach scanning from both ends simultaneously:
8 - From left: tracks maximum seen so far, marks rightmost out-of-order position
9 - From right: tracks minimum seen so far, marks leftmost out-of-order position
10
11 Args:
12 start: Starting index of the window
13 end: Ending index of the window (inclusive)
14
15 Returns:
16 Length of minimum unsorted subarray, or 0 if already sorted
17 """
18 min_from_right = float('inf')
19 max_from_left = float('-inf')
20 left_boundary = -1 # Leftmost position where element is out of order
21 right_boundary = -1 # Rightmost position where element is out of order
22
23 # Scan window from both ends simultaneously
24 for idx in range(start, end + 1):
25 # Check from left side: if current element is less than max seen so far,
26 # it's out of order and could be the right boundary
27 if max_from_left > nums[idx]:
28 right_boundary = idx
29 else:
30 max_from_left = nums[idx]
31
32 # Calculate corresponding index from right side
33 right_idx = end - idx + start
34
35 # Check from right side: if current element is greater than min seen so far,
36 # it's out of order and could be the left boundary
37 if min_from_right < nums[right_idx]:
38 left_boundary = right_idx
39 else:
40 min_from_right = nums[right_idx]
41
42 # If no out-of-order elements found, array is sorted
43 if right_boundary == -1:
44 return 0
45
46 # Return length of unsorted subarray
47 return right_boundary - left_boundary + 1
48
49 n = len(nums)
50 # Process each window of size k and collect results
51 result = []
52 for window_start in range(n - k + 1):
53 window_end = window_start + k - 1
54 unsorted_length = find_unsorted_length(window_start, window_end)
55 result.append(unsorted_length)
56
57 return result
58
1class Solution {
2 private int[] nums;
3 private static final int INFINITY = 1 << 30; // Large value representing infinity (2^30)
4
5 /**
6 * For each window of size k in the array, finds the minimum length of subarray
7 * that needs to be sorted to make the entire window sorted.
8 *
9 * @param nums The input array
10 * @param k The window size
11 * @return Array where ans[i] represents the minimum sort length for window starting at index i
12 */
13 public int[] minSubarraySort(int[] nums, int k) {
14 this.nums = nums;
15 int n = nums.length;
16 int[] answer = new int[n - k + 1]; // Result array for each window
17
18 // Process each window of size k
19 for (int windowStart = 0; windowStart < n - k + 1; windowStart++) {
20 answer[windowStart] = findMinUnsortedLength(windowStart, windowStart + k - 1);
21 }
22
23 return answer;
24 }
25
26 /**
27 * Finds the minimum length of subarray that needs to be sorted within the range [startIdx, endIdx].
28 * Uses a two-pass approach to identify the leftmost and rightmost positions that are out of order.
29 *
30 * @param startIdx Starting index of the window (inclusive)
31 * @param endIdx Ending index of the window (inclusive)
32 * @return Length of minimum subarray to sort, or 0 if already sorted
33 */
34 private int findMinUnsortedLength(int startIdx, int endIdx) {
35 int minValue = INFINITY; // Minimum value seen from right to left
36 int maxValue = -INFINITY; // Maximum value seen from left to right
37 int leftBoundary = -1; // Leftmost position that needs sorting
38 int rightBoundary = -1; // Rightmost position that needs sorting
39
40 // Simultaneously scan from both directions
41 for (int i = startIdx; i <= endIdx; i++) {
42 // Left to right scan: find rightmost position where element is less than max seen so far
43 if (nums[i] < maxValue) {
44 rightBoundary = i; // This position is out of order
45 } else {
46 maxValue = nums[i]; // Update maximum
47 }
48
49 // Right to left scan: find leftmost position where element is greater than min seen so far
50 int mirrorIndex = endIdx - i + startIdx; // Corresponding index from the right
51 if (nums[mirrorIndex] > minValue) {
52 leftBoundary = mirrorIndex; // This position is out of order
53 } else {
54 minValue = nums[mirrorIndex]; // Update minimum
55 }
56 }
57
58 // If no out-of-order elements found, the window is already sorted
59 return rightBoundary == -1 ? 0 : rightBoundary - leftBoundary + 1;
60 }
61}
62
1class Solution {
2public:
3 vector<int> minSubarraySort(vector<int>& nums, int k) {
4 const int INF = 1 << 30; // Large value for initialization
5 int n = nums.size();
6
7 // Lambda function to find minimum subarray to sort within range [start, end]
8 auto findMinSortLength = [&](int start, int end) -> int {
9 int minFromRight = INF; // Minimum value seen from right
10 int maxFromLeft = -INF; // Maximum value seen from left
11 int leftBoundary = -1; // Left boundary of unsorted region
12 int rightBoundary = -1; // Right boundary of unsorted region
13
14 // Iterate through the subarray
15 for (int i = start; i <= end; ++i) {
16 // Check from left to right
17 // If current element is less than max seen so far, it's out of order
18 if (nums[i] < maxFromLeft) {
19 rightBoundary = i; // Update right boundary of unsorted region
20 } else {
21 maxFromLeft = nums[i]; // Update max value
22 }
23
24 // Check from right to left (using mirrored index)
25 int mirroredIndex = end - i + start;
26 // If current element is greater than min seen so far, it's out of order
27 if (nums[mirroredIndex] > minFromRight) {
28 leftBoundary = mirroredIndex; // Update left boundary of unsorted region
29 } else {
30 minFromRight = nums[mirroredIndex]; // Update min value
31 }
32 }
33
34 // If no unsorted region found, return 0; otherwise return length
35 return rightBoundary == -1 ? 0 : rightBoundary - leftBoundary + 1;
36 };
37
38 vector<int> result;
39
40 // Process each subarray of size k
41 for (int i = 0; i < n - k + 1; ++i) {
42 result.push_back(findMinSortLength(i, i + k - 1));
43 }
44
45 return result;
46 }
47};
48
1/**
2 * Finds the minimum length of subarray that needs to be sorted within each sliding window of size k
3 * @param nums - The input array of numbers
4 * @param k - The size of the sliding window
5 * @returns An array where each element represents the minimum sorting length for each window
6 */
7function minSubarraySort(nums: number[], k: number): number[] {
8 const INFINITY = Infinity;
9 const arrayLength = nums.length;
10
11 /**
12 * Calculates the minimum length of subarray that needs to be sorted within the range [startIndex, endIndex]
13 * @param startIndex - The starting index of the window
14 * @param endIndex - The ending index of the window
15 * @returns The length of the minimum subarray that needs to be sorted
16 */
17 const calculateMinSortLength = (startIndex: number, endIndex: number): number => {
18 let minValue = INFINITY;
19 let maxValue = -INFINITY;
20 let leftBoundary = -1;
21 let rightBoundary = -1;
22
23 // Traverse from both ends simultaneously to find the unsorted boundaries
24 for (let currentIndex = startIndex; currentIndex <= endIndex; ++currentIndex) {
25 // Check from left to right: if current element is less than max seen so far,
26 // it needs to be sorted, update right boundary
27 if (nums[currentIndex] < maxValue) {
28 rightBoundary = currentIndex;
29 } else {
30 maxValue = nums[currentIndex];
31 }
32
33 // Calculate the corresponding index from the right side
34 const mirrorIndex = endIndex - currentIndex + startIndex;
35
36 // Check from right to left: if current element is greater than min seen so far,
37 // it needs to be sorted, update left boundary
38 if (nums[mirrorIndex] > minValue) {
39 leftBoundary = mirrorIndex;
40 } else {
41 minValue = nums[mirrorIndex];
42 }
43 }
44
45 // If no unsorted region found, return 0; otherwise return the length
46 return rightBoundary === -1 ? 0 : rightBoundary - leftBoundary + 1;
47 };
48
49 const result: number[] = [];
50
51 // Process each sliding window of size k
52 for (let windowStart = 0; windowStart <= arrayLength - k; ++windowStart) {
53 const windowEnd = windowStart + k - 1;
54 result.push(calculateMinSortLength(windowStart, windowEnd));
55 }
56
57 return result;
58}
59
Time and Space Complexity
The time complexity is O(n × k)
, where n
is the length of the array nums
and k
is the window size. This is because:
- The outer list comprehension iterates
n - k + 1
times (for each possible window starting position) - For each iteration, the function
f(i, j)
is called, which contains a loop that runs exactlyk
times (fromi
toj + 1
wherej = i + k - 1
) - Therefore, the total operations are
(n - k + 1) × k
, which simplifies toO(n × k)
The space complexity is O(1)
for the algorithm itself (excluding the output array). The function f
only uses a constant amount of extra space with variables mi
, mx
, l
, r
, and loop variables, regardless of the input size. The output list has size n - k + 1
, but this is not counted as part of the space complexity of the algorithm itself.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrect Boundary Detection Logic
The Pitfall: A common mistake is incorrectly identifying which elements are "out of order." Developers often confuse the conditions for updating the left and right boundaries. Specifically:
- When scanning left-to-right, finding
nums[idx] < max_from_left
means this element needs to be included in the sorting range (update right boundary) - When scanning right-to-left, finding
nums[idx] > min_from_right
means this element needs to be included in the sorting range (update left boundary)
Why it happens: The confusion arises because the logic seems counterintuitive - when going left-to-right, we update the RIGHT boundary, and when going right-to-left, we update the LEFT boundary.
Example of incorrect implementation:
# WRONG: Swapped boundary updates if max_from_left > nums[idx]: left_boundary = idx # Wrong! Should update right_boundary
Solution: Remember that:
- Left-to-right scan finds the rightmost position that violates sorted order
- Right-to-left scan finds the leftmost position that violates sorted order
2. Off-by-One Errors in Mirror Index Calculation
The Pitfall: When calculating the mirror position for bidirectional scanning, it's easy to make off-by-one errors:
# Common mistakes: right_idx = end - idx # Missing the start offset right_idx = start + end - idx - 1 # Unnecessary -1 right_idx = j - (idx - i) # Overly complex
Solution:
The correct formula is: right_idx = end - idx + start
- When
idx = start
,right_idx = end
(first from left maps to last from right) - When
idx = end
,right_idx = start
(last from left maps to first from right)
3. Mishandling Already Sorted Windows
The Pitfall: Forgetting to handle the case where the window is already sorted, or incorrectly determining this condition:
# WRONG: Checking wrong variable if left_boundary == -1: # Only checks left, not both return 0 # WRONG: Not initializing boundaries properly left_boundary = 0 # Should be -1 to detect no changes
Solution:
Initialize both boundaries to -1
and check if either remains -1
(since both should be updated if any element is out of order):
if right_boundary == -1: # or left_boundary == -1 return 0
4. Confusion Between Strict and Non-Strict Ordering
The Pitfall: Using strict inequality when non-decreasing order (allowing duplicates) is required:
# WRONG: Strict inequality doesn't handle duplicates correctly if max_from_left >= nums[idx]: # Should be > right_boundary = idx
Solution:
Use strict inequalities (>
and <
) to allow duplicate values in non-decreasing order:
max_from_left > nums[idx]
- only update if strictly greatermin_from_right < nums[right_idx]
- only update if strictly less
5. Inefficient Window Processing
The Pitfall: Creating a new array or using sorting for each window unnecessarily:
# INEFFICIENT: Actually sorting to check
def find_unsorted_length(start, end):
window = nums[start:end+1]
sorted_window = sorted(window)
# Then comparing to find differences...
Solution: Use the two-pointer approach without modifying or copying the array. The algorithm only needs to track boundaries, not perform actual sorting.
In a binary min heap, the minimum element can be found in:
Recommended Readings
Stack Intro Imagine you have a pile of books on your desk If you want to add a new book you place it on top If you want to read a book you take it from the top And if you simply want to see which book is on the top you
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Want a Structured Path to Master System Design Too? Don’t Miss This!