Problem
Given an array of integers, find the length of the shortest contiguous subarray that, when sorted in ascending order, makes the entire array sorted. If the array is already sorted, return 0.
- Input:
nums = [2, 6, 4, 8, 10, 9, 15] - Output:
5 - Explanation: Sorting
nums[1..5]โ the subarray[6, 4, 8, 10, 9]โ produces[2, 4, 6, 8, 9, 10, 15].
A counter-example: [1, 2, 3, 4, 5] returns 0 because the array is already sorted and no subarray needs to change.
Intuition
The "dirty" subarray is the region where elements are out of place relative to their neighbors. Scanning left to right, any element that falls below the maximum seen so far is in the wrong position โ it must be inside the subarray we need to sort, so the right boundary is the last such element. The same logic applied from right to left with a running minimum gives the left boundary. Two linear scans with no extra memory produce the exact answer without sorting.
Approach 1 โ Sort and Compare
Sort a copy of the array, then find the first and last positions where the original and sorted versions disagree. Everything between those positions is the subarray that must be sorted.
- Create a sorted copy of the input array.
- Scan left to right to find the first index where the two arrays differ โ this is the left boundary.
- If no difference exists, return 0 (already sorted).
- Scan right to left to find the last index where the two arrays differ โ this is the right boundary.
- Return
right_boundary - left_boundary + 1.
1class Solution:
2 def findUnsortedSubarray(self, nums: List[int]) -> int:
3 sorted_nums = sorted(nums)
4
5 left_boundary = 0
6 while left_boundary < len(nums) and nums[left_boundary] == sorted_nums[left_boundary]:
7 left_boundary += 1
8
9 if left_boundary == len(nums):
10 return 0 # every element matched; array is already sorted
11
12 right_boundary = len(nums) - 1
13 while nums[right_boundary] == sorted_nums[right_boundary]:
14 right_boundary -= 1
15
16 return right_boundary - left_boundary + 1Time: O(n log n) โ dominated by sorting the copy.
Space: O(n) โ the sorted copy holds all n elements.
Approach 2 โ One-Pass Linear Scan
Scan left to right tracking the running maximum: any element strictly less than the running maximum is out of place, so the right boundary is the last such element. Then scan right to left tracking the running minimum: any element strictly greater than the running minimum is out of place, giving the left boundary.
- Initialize
right_boundary = -1andrunning_max = -โ. - Scan left to right: if
nums[i] < running_max, updateright_boundary = i; otherwise updaterunning_max = nums[i]. - Initialize
left_boundary = -1andrunning_min = +โ. - Scan right to left: if
nums[i] > running_min, updateleft_boundary = i; otherwise updaterunning_min = nums[i]. - If
right_boundary == -1, return 0; otherwise returnright_boundary - left_boundary + 1.
1class Solution:
2 def findUnsortedSubarray(self, nums: List[int]) -> int:
3 n = len(nums)
4 left_boundary = -1
5 right_boundary = -1
6 running_max = float('-inf')
7 running_min = float('inf')
8
9 for i in range(n):
10 if nums[i] < running_max:
11 right_boundary = i # out of place from the left; push dirty region rightward
12 else:
13 running_max = nums[i]
14
15 for i in range(n - 1, -1, -1):
16 if nums[i] > running_min:
17 left_boundary = i # out of place from the right; push dirty region leftward
18 else:
19 running_min = nums[i]
20
21 if right_boundary == -1:
22 return 0 # running_max never exceeded any element; array is already sorted
23
24 return right_boundary - left_boundary + 1Time: O(n) โ two linear passes, no sorting.
Space: O(1) โ only a handful of scalar variables regardless of input size.
Complexity Summary
| Approach | Time | Space | When to use |
|---|---|---|---|
| Sort and Compare | O(n log n) | O(n) | When clarity matters more than performance, or to quickly verify correctness |
| One-Pass Linear Scan | O(n) | O(1) | The default interview answer; optimal in both time and space |
Common Mistakes
- Using
โคinstead of<in the forward scan: only elements strictly less than the running maximum are out of place. Changing toโคwould incorrectly treat equal adjacent elements as disordered, inflating the answer. - Forgetting the already-sorted check: if
right_boundaryremains-1and you returnright_boundary - left_boundary + 1anyway, you get-1 - (-1) + 1 = 1, which is wrong. The guard onright_boundary == -1is essential. - Scanning right to left with the wrong comparison: the backward pass catches elements greater than the running minimum (meaning their left neighbor should be โค them but isn't). Swapping to
<produces an incorrect left boundary. - Initializing
left_boundaryto 0 instead of -1: if the leftmost element is already in its correct position, starting at 0 overstates the answer by 1. - In sort-and-compare, starting the right boundary scan from
left_boundaryinstead of the end: the right boundary must scan from the far end of the array to find the last mismatch, not the first one after the left boundary.
Related Problems
Sort Colorsโ partitions an array into sorted regions in-place, requiring the same boundary-detection thinking about where elements belongMove Zeroesโ in-place rearrangement that identifies a "clean" ordered region versus a region that needs fixing, same general flavorNext Permutationโ scans right-to-left to find the first element that breaks descending order, the same backward disorder-detection technique used hereMerge Intervalsโ works with overlapping spans in a sorted array, analogous to finding and collapsing a "dirty" regionSubarray Sum Equals Kโ another subarray boundary problem solved by clever linear scanning rather than brute-force enumeration