1574. Shortest Subarray to be Removed to Make Array Sorted
Problem Description
You are given an integer array arr
. Your task is to remove a subarray (which can be empty) from arr
such that the remaining elements form a non-decreasing sequence.
A non-decreasing sequence means each element is less than or equal to the next element (i.e., arr[i] <= arr[i+1]
).
A subarray is a contiguous portion of the array - meaning you can only remove elements that are next to each other, not individual elements from different positions.
Your goal is to find the minimum length of the subarray that needs to be removed to make the remaining array non-decreasing.
For example:
- If
arr = [1, 2, 3, 10, 4, 2, 3, 5]
, you could remove the subarray[10, 4, 2]
(length 3) to get[1, 2, 3, 3, 5]
, which is non-decreasing. - If the array is already non-decreasing like
[1, 2, 3, 4, 5]
, you don't need to remove anything, so return0
.
The key insight is that after removing a subarray, you're essentially keeping a prefix and a suffix of the original array, and these must combine to form a non-decreasing sequence.
Intuition
When we remove a subarray from the middle, we're essentially keeping a prefix (left part) and a suffix (right part) of the original array. For the remaining array to be non-decreasing, two conditions must hold:
- The prefix itself must be non-decreasing
- The suffix itself must be non-decreasing
- The last element of the prefix must be ≤ the first element of the suffix
This observation leads us to think: what if we first identify the longest non-decreasing prefix and the longest non-decreasing suffix?
Let's say the longest non-decreasing prefix ends at index i
, and the longest non-decreasing suffix starts at index j
.
If i >= j
, it means these two parts overlap or touch each other - the entire array is already non-decreasing! We need to remove nothing, so return 0
.
Otherwise, we have three strategies:
- Keep only the prefix: remove everything after index
i
, which removesn - i - 1
elements - Keep only the suffix: remove everything before index
j
, which removesj
elements - Keep both a prefix and a suffix: we need to ensure they can connect properly
For strategy 3, we can iterate through each possible endpoint of the prefix (from index 0
to i
). For each prefix ending at index l
, we need to find where in the suffix we can connect. Since the suffix arr[j..n-1]
is non-decreasing, we can use binary search to find the first position r
in the suffix where arr[r] >= arr[l]
. Then we remove everything between l
and r
, which is r - l - 1
elements.
The minimum among all these possibilities gives us our answer.
Learn more about Stack, Two Pointers, Binary Search and Monotonic Stack patterns.
Solution Approach
The implementation follows a two-pointer approach combined with binary search:
Step 1: Find the longest non-decreasing prefix
i = 0 while i + 1 < n and arr[i] <= arr[i + 1]: i += 1
We iterate from the beginning and stop when we find the first position where arr[i] > arr[i + 1]
. The prefix arr[0..i]
is non-decreasing.
Step 2: Find the longest non-decreasing suffix
j = n - 1 while j - 1 >= 0 and arr[j - 1] <= arr[j]: j -= 1
We iterate from the end and stop when we find the first position where arr[j - 1] > arr[j]
. The suffix arr[j..n-1]
is non-decreasing.
Step 3: Check if array is already non-decreasing
if i >= j: return 0
If the prefix and suffix overlap or meet, the entire array is non-decreasing.
Step 4: Initialize answer with simple cases
ans = min(n - i - 1, j)
n - i - 1
: Remove everything after the prefix (keep only prefix)j
: Remove everything before the suffix (keep only suffix)
Step 5: Try combining prefix and suffix
for l in range(i + 1):
r = bisect_left(arr, arr[l], lo=j)
ans = min(ans, r - l - 1)
For each possible prefix ending at index l
:
- Use binary search (
bisect_left
) to find the first positionr
in the suffixarr[j..n-1]
wherearr[r] >= arr[l]
- The
lo=j
parameter ensures we only search within the suffix portion - Calculate the length of subarray to remove:
r - l - 1
(everything betweenl
andr
) - Update the minimum answer
The algorithm efficiently explores all valid ways to remove a contiguous subarray, ensuring the remaining parts form a non-decreasing sequence. The time complexity is O(n log n)
due to the binary search performed for each prefix position.
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 algorithm with arr = [1, 2, 3, 10, 4, 2, 3, 5]
.
Step 1: Find longest non-decreasing prefix
- Start at index 0:
1 <= 2
✓, continue - Index 1:
2 <= 3
✓, continue - Index 2:
3 <= 10
✓, continue - Index 3:
10 <= 4
✗, stop
The longest non-decreasing prefix is [1, 2, 3, 10]
, ending at i = 3
.
Step 2: Find longest non-decreasing suffix
- Start at index 7:
3 <= 5
✓, continue - Index 6:
2 <= 3
✓, continue - Index 5:
4 <= 2
✗, stop
The longest non-decreasing suffix is [2, 3, 5]
, starting at j = 5
.
Step 3: Check if already non-decreasing
- Is
i >= j
? Is3 >= 5
? No, so we continue.
Step 4: Initialize with simple cases
- Keep only prefix: remove indices 4-7, length =
8 - 3 - 1 = 4
- Keep only suffix: remove indices 0-4, length =
5
- Initial
ans = min(4, 5) = 4
Step 5: Try combining prefix and suffix
For each prefix ending position l
from 0 to 3:
-
l = 0 (prefix =
[1]
):- Binary search in suffix
[2, 3, 5]
for first element ≥ 1 - Found at index 5 (value = 2)
- Remove indices 1-4, length =
5 - 0 - 1 = 4
ans = min(4, 4) = 4
- Binary search in suffix
-
l = 1 (prefix =
[1, 2]
):- Binary search in suffix
[2, 3, 5]
for first element ≥ 2 - Found at index 5 (value = 2)
- Remove indices 2-4, length =
5 - 1 - 1 = 3
ans = min(4, 3) = 3
- Binary search in suffix
-
l = 2 (prefix =
[1, 2, 3]
):- Binary search in suffix
[2, 3, 5]
for first element ≥ 3 - Found at index 6 (value = 3)
- Remove indices 3-5, length =
6 - 2 - 1 = 3
ans = min(3, 3) = 3
- Binary search in suffix
-
l = 3 (prefix =
[1, 2, 3, 10]
):- Binary search in suffix
[2, 3, 5]
for first element ≥ 10 - Not found (returns index 8, beyond array)
- Remove indices 4-7, length =
8 - 3 - 1 = 4
ans = min(3, 4) = 3
- Binary search in suffix
Final answer: 3
The optimal solution removes the subarray [10, 4, 2]
at indices 3-5, leaving [1, 2, 3, 3, 5]
which is non-decreasing.
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4class Solution:
5 def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
6 n = len(arr)
7
8 # Find the longest non-decreasing prefix
9 left_end = 0
10 while left_end + 1 < n and arr[left_end] <= arr[left_end + 1]:
11 left_end += 1
12
13 # Find the longest non-decreasing suffix
14 right_start = n - 1
15 while right_start - 1 >= 0 and arr[right_start - 1] <= arr[right_start]:
16 right_start -= 1
17
18 # If the entire array is already non-decreasing
19 if left_end >= right_start:
20 return 0
21
22 # Option 1: Keep only the prefix or only the suffix
23 min_removal = min(n - left_end - 1, # Remove everything after prefix
24 right_start) # Remove everything before suffix
25
26 # Option 2: Keep some prefix and some suffix, remove middle part
27 # For each element in the prefix, find where it can connect in the suffix
28 for prefix_idx in range(left_end + 1):
29 # Find the leftmost position in suffix where arr[prefix_idx] can connect
30 suffix_idx = bisect_left(arr, arr[prefix_idx], lo=right_start)
31
32 # Calculate elements to remove between prefix_idx and suffix_idx
33 removal_length = suffix_idx - prefix_idx - 1
34 min_removal = min(min_removal, removal_length)
35
36 return min_removal
37
1class Solution {
2 /**
3 * Finds the length of the shortest subarray to remove to make the array sorted.
4 * Strategy: Find the longest sorted prefix and suffix, then try to connect them.
5 *
6 * @param arr the input array
7 * @return the length of the shortest subarray to remove
8 */
9 public int findLengthOfShortestSubarray(int[] arr) {
10 int n = arr.length;
11
12 // Find the longest non-decreasing prefix ending at index leftEnd
13 int leftEnd = 0;
14 while (leftEnd + 1 < n && arr[leftEnd] <= arr[leftEnd + 1]) {
15 leftEnd++;
16 }
17
18 // Find the longest non-decreasing suffix starting at index rightStart
19 int rightStart = n - 1;
20 while (rightStart - 1 >= 0 && arr[rightStart - 1] <= arr[rightStart]) {
21 rightStart--;
22 }
23
24 // If the entire array is already sorted (prefix and suffix overlap)
25 if (leftEnd >= rightStart) {
26 return 0;
27 }
28
29 // Option 1: Keep only the prefix (remove everything after leftEnd)
30 // Option 2: Keep only the suffix (remove everything before rightStart)
31 int minLength = Math.min(n - leftEnd - 1, rightStart);
32
33 // Option 3: Try to connect prefix and suffix by finding valid combinations
34 // For each element in the prefix, find the first valid element in the suffix
35 for (int prefixIndex = 0; prefixIndex <= leftEnd; prefixIndex++) {
36 // Binary search for the first element in suffix >= arr[prefixIndex]
37 int suffixIndex = binarySearchFirstGreaterOrEqual(arr, arr[prefixIndex], rightStart);
38 // Calculate the length of elements to remove between prefixIndex and suffixIndex
39 minLength = Math.min(minLength, suffixIndex - prefixIndex - 1);
40 }
41
42 return minLength;
43 }
44
45 /**
46 * Binary search to find the first index where arr[index] >= target.
47 * Searches in the range [startIndex, arr.length).
48 *
49 * @param arr the array to search in
50 * @param target the target value to compare against
51 * @param startIndex the starting index for the search
52 * @return the first index where arr[index] >= target
53 */
54 private int binarySearchFirstGreaterOrEqual(int[] arr, int target, int startIndex) {
55 int left = startIndex;
56 int right = arr.length;
57
58 while (left < right) {
59 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
60 if (arr[mid] >= target) {
61 // Found a valid element, but check if there's an earlier one
62 right = mid;
63 } else {
64 // Current element is too small, search in the right half
65 left = mid + 1;
66 }
67 }
68
69 return left;
70 }
71}
72
1class Solution {
2public:
3 int findLengthOfShortestSubarray(vector<int>& arr) {
4 int n = arr.size();
5
6 // Find the longest non-decreasing prefix
7 int leftEnd = 0;
8 while (leftEnd + 1 < n && arr[leftEnd] <= arr[leftEnd + 1]) {
9 ++leftEnd;
10 }
11
12 // Find the longest non-decreasing suffix
13 int rightStart = n - 1;
14 while (rightStart - 1 >= 0 && arr[rightStart - 1] <= arr[rightStart]) {
15 --rightStart;
16 }
17
18 // If the entire array is already non-decreasing
19 if (leftEnd >= rightStart) {
20 return 0;
21 }
22
23 // Calculate minimum removal length by either:
24 // 1. Keeping only the prefix (remove everything after leftEnd)
25 // 2. Keeping only the suffix (remove everything before rightStart)
26 int minRemovalLength = min(n - 1 - leftEnd, rightStart);
27
28 // Try to connect prefix and suffix by finding valid combinations
29 for (int prefixIdx = 0; prefixIdx <= leftEnd; ++prefixIdx) {
30 // Find the first element in suffix that is >= arr[prefixIdx]
31 int suffixIdx = lower_bound(arr.begin() + rightStart, arr.end(), arr[prefixIdx]) - arr.begin();
32
33 // Calculate elements to remove between prefixIdx and suffixIdx
34 minRemovalLength = min(minRemovalLength, suffixIdx - prefixIdx - 1);
35 }
36
37 return minRemovalLength;
38 }
39};
40
1function findLengthOfShortestSubarray(arr: number[]): number {
2 const n = arr.length;
3
4 // Find the longest non-decreasing prefix
5 let leftEnd = 0;
6 while (leftEnd + 1 < n && arr[leftEnd] <= arr[leftEnd + 1]) {
7 leftEnd++;
8 }
9
10 // Find the longest non-decreasing suffix
11 let rightStart = n - 1;
12 while (rightStart - 1 >= 0 && arr[rightStart - 1] <= arr[rightStart]) {
13 rightStart--;
14 }
15
16 // If the entire array is already non-decreasing
17 if (leftEnd >= rightStart) {
18 return 0;
19 }
20
21 // Calculate minimum removal length by either:
22 // 1. Keeping only the prefix (remove everything after leftEnd)
23 // 2. Keeping only the suffix (remove everything before rightStart)
24 let minRemovalLength = Math.min(n - 1 - leftEnd, rightStart);
25
26 // Try to connect prefix and suffix by finding valid combinations
27 for (let prefixIdx = 0; prefixIdx <= leftEnd; prefixIdx++) {
28 // Find the first element in suffix that is >= arr[prefixIdx]
29 let suffixIdx = lowerBound(arr, rightStart, n, arr[prefixIdx]);
30
31 // Calculate elements to remove between prefixIdx and suffixIdx
32 minRemovalLength = Math.min(minRemovalLength, suffixIdx - prefixIdx - 1);
33 }
34
35 return minRemovalLength;
36}
37
38// Helper function to find the first element >= target in arr[start...end)
39function lowerBound(arr: number[], start: number, end: number, target: number): number {
40 let left = start;
41 let right = end;
42
43 while (left < right) {
44 const mid = Math.floor((left + right) / 2);
45 if (arr[mid] < target) {
46 left = mid + 1;
47 } else {
48 right = mid;
49 }
50 }
51
52 return left;
53}
54
Time and Space Complexity
The time complexity is O(n log n)
, where n
is the length of the array. This complexity comes from:
- The first while loop runs at most
n
times to find the longest non-decreasing prefix:O(n)
- The second while loop runs at most
n
times to find the longest non-decreasing suffix:O(n)
- The for loop iterates through at most
i + 1
elements (which is at mostn
):O(n)
- Inside the for loop,
bisect_left
performs binary search on the suffix portion of the array, which takesO(log n)
time - Therefore, the overall time complexity is
O(n) + O(n) + O(n × log n) = O(n log n)
The space complexity is O(1)
as the algorithm only uses a constant amount of extra space for variables i
, j
, l
, r
, ans
, and n
, regardless of the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
Pitfall 1: Incorrect Binary Search Usage for Finding Valid Suffix Connection
The Problem:
Using bisect_left(arr, arr[prefix_idx], lo=right_start)
doesn't guarantee a valid connection between prefix and suffix. The binary search finds where arr[prefix_idx]
would fit in the sorted suffix, but it doesn't verify that the value at that position actually exists or forms a valid non-decreasing connection.
Why It Happens:
bisect_left
returns an insertion point, which could ben
(beyond array bounds)- Even if the index is valid, we need
arr[suffix_idx] >= arr[prefix_idx]
for a valid connection - The suffix starting at
right_start
is non-decreasing, but we need to ensure the connection point exists
Correct Solution:
for prefix_idx in range(left_end + 1):
# Use binary search to find insertion point
suffix_idx = bisect_left(arr, arr[prefix_idx], lo=right_start)
# Ensure suffix_idx is within bounds
if suffix_idx < n:
# Valid connection exists
removal_length = suffix_idx - prefix_idx - 1
min_removal = min(min_removal, removal_length)
Pitfall 2: Off-by-One Error in Removal Length Calculation
The Problem: Confusion about what indices represent (inclusive vs exclusive) leads to incorrect removal length calculations.
Why It Happens:
left_end
is the last index of the valid prefix (inclusive)right_start
is the first index of the valid suffix (inclusive)- When calculating removal length between
prefix_idx
andsuffix_idx
, it's easy to miscount
Correct Understanding:
- If keeping prefix up to index
i
and suffix from indexj
, we remove indices[i+1, j-1]
- Removal length =
j - i - 1
=(j-1) - (i+1) + 1
- Example: If
i=2
andj=5
, we keep[0,1,2]
and[5,...]
, removing[3,4]
(length 2)
Pitfall 3: Not Handling Edge Cases Properly
The Problem: Missing edge cases like single-element arrays or arrays with all equal elements.
Why It Happens:
- The algorithm assumes there are distinct increasing/decreasing patterns
- Equal consecutive elements need special consideration for "non-decreasing" vs "strictly increasing"
Correct Solution:
Ensure the comparison uses <=
(not <
) for non-decreasing sequences:
# Correct: non-decreasing allows equal elements while left_end + 1 < n and arr[left_end] <= arr[left_end + 1]: left_end += 1 # Incorrect: would stop at equal elements while left_end + 1 < n and arr[left_end] < arr[left_end + 1]: left_end += 1
Which data structure is used to implement priority queue?
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
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
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
Want a Structured Path to Master System Design Too? Don’t Miss This!