581. Shortest Unsorted Continuous Subarray
Problem Description
You are given an integer array nums
. Your task is to find the shortest continuous subarray within this array such that if you sort only this subarray in non-decreasing order (ascending order), the entire array becomes sorted in non-decreasing order.
For example, if you have an array like [2, 6, 4, 8, 10, 9, 15]
, you need to identify that sorting the subarray [6, 4, 8, 10, 9]
(from index 1 to 5) would make the entire array sorted as [2, 4, 6, 8, 9, 10, 15]
.
The problem asks you to return the length of this shortest subarray that needs to be sorted. If the array is already sorted, the length would be 0.
The solution approach works by:
- Creating a sorted version of the original array
- Finding the leftmost position where the original array differs from the sorted array (this is where the unsorted portion begins)
- Finding the rightmost position where the original array differs from the sorted array (this is where the unsorted portion ends)
- The distance between these two positions plus 1 gives the length of the subarray that needs to be sorted
The code uses two pointers l
and r
starting from the beginning and end of the array respectively. It moves l
forward as long as elements match between the original and sorted arrays, and moves r
backward as long as elements match. The formula r - l + 1
gives the length of the subarray between these positions.
Intuition
The key insight is that if we need to sort only a portion of the array to make the entire array sorted, then the elements outside this portion must already be in their correct final positions.
Think about what happens when we sort an array - each element moves to its correct position. If an element is already in its correct position, it doesn't need to move. So we can identify which elements need to be rearranged by comparing the original array with what it should look like when fully sorted.
When we create a sorted version of the array and compare it with the original:
- Elements at the beginning that are already in their correct positions don't need to be included in the subarray we sort
- Elements at the end that are already in their correct positions also don't need to be included
The elements that differ between the original and sorted arrays are exactly those that need to be rearranged. The leftmost difference marks where the unsorted portion begins, and the rightmost difference marks where it ends.
For example, with nums = [2, 6, 4, 8, 10, 9, 15]
:
- Sorted version:
[2, 4, 6, 8, 9, 10, 15]
- Comparing position by position:
- Position 0:
2 == 2
✓ (already correct) - Position 1:
6 != 4
✗ (needs sorting) - Position 2:
4 != 6
✗ (needs sorting) - ...continuing...
- Position 5:
9 != 10
✗ (needs sorting) - Position 6:
15 == 15
✓ (already correct)
- Position 0:
The differences occur from index 1 to 5, so we need to sort that subarray of length 5 - 1 + 1 = 5
.
This approach naturally handles edge cases - if the array is already sorted, no positions will differ, and the pointers will cross, resulting in a length of 0.
Learn more about Stack, Greedy, Two Pointers, Sorting and Monotonic Stack patterns.
Solution Approach
The implementation uses a sorting-based approach to find the shortest unsorted subarray:
-
Create a sorted copy: First, we create a sorted version of the input array using
sorted(nums)
. This gives us the target state that we want to achieve. -
Initialize two pointers: We set up two pointers:
l
(left pointer) starting at index 0r
(right pointer) starting at the last indexlen(nums) - 1
-
Find the left boundary: We move the left pointer
l
forward as long as:l <= r
(pointers haven't crossed)nums[l] == arr[l]
(element at positionl
is already in its correct sorted position)
When this loop stops,
l
points to the first position where the element differs from its sorted position, marking the start of the unsorted subarray. -
Find the right boundary: Similarly, we move the right pointer
r
backward as long as:l <= r
(pointers haven't crossed)nums[r] == arr[r]
(element at positionr
is already in its correct sorted position)
When this loop stops,
r
points to the last position where the element differs from its sorted position, marking the end of the unsorted subarray. -
Calculate the length: The length of the shortest unsorted subarray is
r - l + 1
.
The condition l <= r
in both loops handles the edge case where the array is already sorted. In this case, l
will increment all the way past r
, and the formula r - l + 1
will give a non-positive number, which effectively returns 0 (since at that point l > r
).
Time Complexity: O(n log n)
due to the sorting operation
Space Complexity: O(n)
for storing the sorted copy of the array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the array nums = [1, 3, 2, 4, 5]
.
Step 1: Create sorted version
- Original:
[1, 3, 2, 4, 5]
- Sorted:
[1, 2, 3, 4, 5]
Step 2: Initialize pointers
l = 0
(pointing to first element)r = 4
(pointing to last element)
Step 3: Find left boundary
- Check position 0:
nums[0] = 1
,sorted[0] = 1
→ They match! Movel
forward l = 1
- Check position 1:
nums[1] = 3
,sorted[1] = 2
→ They differ! Stop here - Left boundary found at index
l = 1
Step 4: Find right boundary
- Check position 4:
nums[4] = 5
,sorted[4] = 5
→ They match! Mover
backward r = 3
- Check position 3:
nums[3] = 4
,sorted[3] = 4
→ They match! Mover
backward r = 2
- Check position 2:
nums[2] = 2
,sorted[2] = 3
→ They differ! Stop here - Right boundary found at index
r = 2
Step 5: Calculate length
- The unsorted subarray spans from index 1 to index 2
- Length =
r - l + 1 = 2 - 1 + 1 = 2
Verification: If we sort the subarray [3, 2]
at indices 1-2, it becomes [2, 3]
, making the entire array [1, 2, 3, 4, 5]
which is sorted! ✓
The algorithm correctly identifies that we need to sort a subarray of length 2.
Solution Implementation
1class Solution:
2 def findUnsortedSubarray(self, nums: List[int]) -> int:
3 """
4 Find the shortest subarray that needs to be sorted to make the entire array sorted.
5
6 Args:
7 nums: Input array of integers
8
9 Returns:
10 Length of the shortest unsorted subarray
11 """
12 # Create a sorted version of the input array
13 sorted_nums = sorted(nums)
14
15 # Initialize left and right pointers
16 left = 0
17 right = len(nums) - 1
18
19 # Find the leftmost position where the element differs from sorted array
20 while left <= right and nums[left] == sorted_nums[left]:
21 left += 1
22
23 # Find the rightmost position where the element differs from sorted array
24 while left <= right and nums[right] == sorted_nums[right]:
25 right -= 1
26
27 # Calculate the length of the unsorted subarray
28 # If left > right, the array is already sorted, so return 0
29 # Otherwise, return the length including both endpoints
30 return right - left + 1
31
1class Solution {
2 /**
3 * Finds the length of the shortest continuous subarray that, if sorted,
4 * would make the entire array sorted in ascending order.
5 *
6 * @param nums The input integer array
7 * @return The length of the shortest unsorted subarray
8 */
9 public int findUnsortedSubarray(int[] nums) {
10 // Create a sorted copy of the original array
11 int[] sortedArray = nums.clone();
12 Arrays.sort(sortedArray);
13
14 // Initialize left and right pointers
15 int leftIndex = 0;
16 int rightIndex = sortedArray.length - 1;
17
18 // Find the leftmost position where elements differ
19 // This marks the start of the unsorted subarray
20 while (leftIndex <= rightIndex && nums[leftIndex] == sortedArray[leftIndex]) {
21 leftIndex++;
22 }
23
24 // Find the rightmost position where elements differ
25 // This marks the end of the unsorted subarray
26 while (leftIndex <= rightIndex && nums[rightIndex] == sortedArray[rightIndex]) {
27 rightIndex--;
28 }
29
30 // Calculate the length of the unsorted subarray
31 // If leftIndex > rightIndex, the array is already sorted (returns 0)
32 return rightIndex - leftIndex + 1;
33 }
34}
35
1class Solution {
2public:
3 int findUnsortedSubarray(vector<int>& nums) {
4 // Create a sorted copy of the input array
5 vector<int> sortedArray = nums;
6 sort(sortedArray.begin(), sortedArray.end());
7
8 // Initialize left and right pointers to track the unsorted subarray boundaries
9 int left = 0;
10 int right = nums.size() - 1;
11
12 // Find the leftmost position where the element differs from sorted array
13 // This marks the start of the unsorted subarray
14 while (left <= right && sortedArray[left] == nums[left]) {
15 left++;
16 }
17
18 // Find the rightmost position where the element differs from sorted array
19 // This marks the end of the unsorted subarray
20 while (left <= right && sortedArray[right] == nums[right]) {
21 right--;
22 }
23
24 // Calculate the length of the shortest unsorted subarray
25 // If left > right, the array is already sorted, returning 0
26 return right - left + 1;
27 }
28};
29
1/**
2 * Finds the length of the shortest continuous subarray that, if sorted,
3 * would make the entire array sorted in ascending order.
4 *
5 * @param nums - The input array of numbers
6 * @returns The length of the shortest unsorted subarray
7 */
8function findUnsortedSubarray(nums: number[]): number {
9 // Create a sorted copy of the original array
10 const sortedArray: number[] = [...nums];
11 sortedArray.sort((a: number, b: number) => a - b);
12
13 // Initialize left and right pointers to the array boundaries
14 let leftIndex: number = 0;
15 let rightIndex: number = sortedArray.length - 1;
16
17 // Find the leftmost position where sorted and original arrays differ
18 while (leftIndex <= rightIndex && sortedArray[leftIndex] === nums[leftIndex]) {
19 leftIndex++;
20 }
21
22 // Find the rightmost position where sorted and original arrays differ
23 while (leftIndex <= rightIndex && sortedArray[rightIndex] === nums[rightIndex]) {
24 rightIndex--;
25 }
26
27 // Calculate the length of the unsorted subarray
28 // If leftIndex > rightIndex, the array is already sorted (returns 0)
29 return rightIndex - leftIndex + 1;
30}
31
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the array. This is dominated by the sorting operation sorted(nums)
, which uses Python's Timsort algorithm with O(n × log n)
time complexity. The two while loops that follow each iterate through the array at most once, contributing O(n)
time, but this is absorbed by the larger sorting complexity.
The space complexity is O(n)
, where n
is the length of the array. This is because the sorted()
function creates a new sorted copy of the input array nums
, which requires O(n)
additional space to store all elements.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Duplicate Elements Mishandling
One common pitfall occurs when dealing with duplicate elements at the boundaries of the unsorted region. Consider the array [1, 2, 3, 3, 3]
. The current approach works correctly, but developers often mistakenly try to optimize by skipping duplicates, which can lead to incorrect boundary detection.
Example of problematic scenario:
nums = [1, 3, 2, 3, 3] # Sorted: [1, 2, 3, 3, 3]
If someone tries to "optimize" by skipping equal adjacent elements before comparing with the sorted array, they might miss that the 3
at index 1 needs to be part of the subarray to be sorted.
2. Edge Case: Already Sorted Array
While the current solution handles this correctly through the left <= right
condition, a common mistake is forgetting to check if the pointers have crossed. Without this check, the code might continue comparing beyond valid boundaries or return incorrect negative values.
Incorrect implementation example:
# WRONG: Missing pointer crossing check while nums[left] == sorted_nums[left]: # Can cause index out of bounds left += 1
3. Memory Optimization Attempt Leading to Incorrect Results
Developers might try to avoid creating a sorted copy to save space, attempting to find boundaries by just comparing adjacent elements or looking for inversions. This approach often fails for cases like:
nums = [1, 3, 2, 2, 2] # Simply looking for inversions might incorrectly identify only [3, 2] as needing sorting # But actually [3, 2, 2, 2] needs to be sorted to get [1, 2, 2, 2, 3]
4. Alternative O(n) Solution Complexity
While an O(n) solution exists using min/max tracking, it's more error-prone:
def findUnsortedSubarray(self, nums: List[int]) -> int:
n = len(nums)
if n <= 1:
return 0
# Find initial boundaries
left = n
right = 0
# Track max from left and min from right
max_so_far = float('-inf')
min_so_far = float('inf')
# Find right boundary
for i in range(n):
max_so_far = max(max_so_far, nums[i])
if nums[i] < max_so_far:
right = i
# Find left boundary
for i in range(n-1, -1, -1):
min_so_far = min(min_so_far, nums[i])
if nums[i] > min_so_far:
left = i
return 0 if right <= left else right - left + 1
This O(n) approach is harder to understand and more prone to implementation errors, making the sorting-based solution often preferable despite its higher time complexity.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
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!