Shortest Subarray to be Removed to Make Array Sorted
MedPrompt
Given an integer array arr, remove a subarray (can be empty) from arr such that the remaining elements in arr are non-decreasing.
Return the length of the shortest subarray to remove.
A subarray is a contiguous subsequence of the array.
Example 1:
Input: arr = [1,2,3,10,4,2,3,5]
Output: 3
Explanation: The shortest subarray we can remove is [10,4,2] of length 3. The remaining elements after that will be [1,2,3,3,5] which are sorted.
Another correct solution is to remove the subarray [3,10,4].Example 2:
Input: arr = [5,4,3,2,1]
Output: 4
Explanation: Since the array is strictly decreasing, we can only keep a single element. Therefore we need to remove a subarray of length 4, either [5,4,3,2] or [4,3,2,1].Example 3:
Input: arr = [1,2,3]
Output: 0
Explanation: The array is already non-decreasing. We do not need to remove any elements.
Constraints:
1 <= arr.length <= 1050 <= arr[i] <= 109
Approaches
3 approaches with complexity analysis and trade-offs.
This approach involves trying every possible contiguous subarray for removal. For each potential subarray removal, we construct the remaining array and check if it is sorted in non-decreasing order. We keep track of the length of the shortest subarray that results in a sorted array.
Algorithm
- Initialize
minLengthton, the maximum possible length to remove. - Use a nested loop to iterate through all possible start indices
i(from0ton) and end indicesj(fromiton) of a subarray to be removed. - For each pair
(i, j), the subarrayarr[i...j-1]is considered for removal. - Create a temporary list by concatenating the prefix
arr[0...i-1]and the suffixarr[j...n-1]. - Iterate through the temporary list to check if it is sorted in non-decreasing order.
- If the list is sorted, update
minLengthwith the length of the removed subarray, which isj - i. - After checking all possibilities, return
minLength.
Walkthrough
The most straightforward way to solve the problem is to consider every single subarray that could be removed. We can define a subarray to be removed by its start and end indices. Let's say we remove the subarray arr[i...j-1]. The remaining parts of the array are the prefix arr[0...i-1] and the suffix arr[j...n-1]. We can form a new array by combining these two parts and then check if this new array is sorted. We do this for all possible i and j and keep track of the minimum length j - i that yields a sorted array.
class Solution { public int findLengthOfShortestSubarray(int[] arr) { int n = arr.length; int minLength = n - 1; // i is the start of the removed subarray // j is the end of the removed subarray + 1 for (int i = 0; i <= n; i++) { for (int j = i; j <= n; j++) { java.util.List<Integer> temp = new java.util.ArrayList<>(); for (int k = 0; k < i; k++) { temp.add(arr[k]); } for (int k = j; k < n; k++) { temp.add(arr[k]); } boolean isSorted = true; for (int k = 0; k < temp.size() - 1; k++) { if (temp.get(k) > temp.get(k + 1)) { isSorted = false; break; } } if (isSorted) { minLength = Math.min(minLength, j - i); } } } return minLength; }}Complexity
Time
O(N^3), where N is the number of elements in the array. The two nested loops for `i` and `j` result in O(N^2) pairs. For each pair, creating and checking the temporary array takes O(N) time.
Space
O(N), where N is the number of elements in the array. This is because in each iteration, a temporary list of size up to N is created to check for sortedness.
Trade-offs
Pros
Simple to understand and implement.
Correctly solves the problem for small inputs.
Cons
Extremely inefficient due to its cubic time complexity.
Will result in a 'Time Limit Exceeded' error on platforms like LeetCode for the given constraints.
Uses extra space proportional to the input size in each iteration.
This approach improves upon the pure brute force method. Instead of rebuilding and re-checking the remaining array in each iteration, we can determine if the remaining parts form a sorted sequence more efficiently. The key observation is that the remaining array is sorted if and only if the prefix part is sorted, the suffix part is sorted, and the last element of the prefix is less than or equal to the first element of the suffix.
Algorithm
- Precompute a boolean array
prefixSortedwhereprefixSorted[k]is true ifarr[0...k]is non-decreasing. This takes O(N) time. - Precompute a boolean array
suffixSortedwheresuffixSorted[k]is true ifarr[k...n-1]is non-decreasing. This also takes O(N) time. - Initialize
minLengthton. - Iterate through all possible start indices
i(from0ton) and end indicesj(fromiton) of the subarray to remove. - For each
(i, j), check the validity in O(1) time:- The prefix
arr[0...i-1]is sorted (checkprefixSorted[i-1]). - The suffix
arr[j...n-1]is sorted (checksuffixSorted[j]). - The last element of the prefix is less than or equal to the first element of the suffix (
arr[i-1] <= arr[j]).
- The prefix
- Handle edge cases where the prefix or suffix is empty.
- If all conditions are met, update
minLength = min(minLength, j - i). - Return
minLength.
Walkthrough
We can optimize the O(N^3) brute force by avoiding the O(N) check inside the loops. The check for whether the remaining array is sorted can be broken down into three conditions: the prefix part must be sorted, the suffix part must be sorted, and the connection between them must be valid. We can precompute the sorted status of all possible prefixes and suffixes in O(N) time and store them in boolean arrays. Then, inside the O(N^2) loops, we can perform the check in O(1) time using these precomputed values.
class Solution { public int findLengthOfShortestSubarray(int[] arr) { int n = arr.length; if (n <= 1) { return 0; } boolean[] prefixSorted = new boolean[n]; prefixSorted[0] = true; for (int k = 1; k < n; k++) { prefixSorted[k] = prefixSorted[k - 1] && (arr[k - 1] <= arr[k]); } boolean[] suffixSorted = new boolean[n]; suffixSorted[n - 1] = true; for (int k = n - 2; k >= 0; k--) { suffixSorted[k] = suffixSorted[k + 1] && (arr[k] <= arr[k + 1]); } int minLength = n - 1; for (int i = 0; i <= n; i++) { for (int j = i; j <= n; j++) { boolean isPrefixOk = (i == 0) || prefixSorted[i - 1]; boolean isSuffixOk = (j == n) || suffixSorted[j]; boolean isConnectionOk = (i == 0) || (j == n) || (arr[i - 1] <= arr[j]); if (isPrefixOk && isSuffixOk && isConnectionOk) { minLength = Math.min(minLength, j - i); } } } return minLength; }}Complexity
Time
O(N^2). Precomputation takes O(N), but the nested loops for `i` and `j` dominate the runtime with O(N^2) iterations, each taking O(1) time.
Space
O(N) for the `prefixSorted` and `suffixSorted` boolean arrays.
Trade-offs
Pros
Significantly faster than the O(N^3) brute-force approach.
The logic is a clear optimization of the naive solution.
Cons
The O(N^2) time complexity is still too slow for the given constraints.
Requires O(N) extra space for the precomputation arrays.
This is the most efficient approach with linear time complexity. The core idea is that any valid remaining array must be formed by a non-decreasing prefix of the original array and a non-decreasing suffix of the original array. We first identify the longest such prefix and suffix. Then, we explore three possibilities for the shortest removal: removing everything after the prefix, removing everything before the suffix, or merging a part of the prefix with a part of the suffix.
Algorithm
- Find the rightmost index
leftsuch that the prefixarr[0...left]is non-decreasing. Ifleftreaches the end of the array, it's already sorted, so return 0. - Find the leftmost index
rightsuch that the suffixarr[right...n-1]is non-decreasing. - The problem is now reduced to finding the shortest subarray to remove. There are three possibilities:
- Remove the suffix starting from
left + 1. The length isn - 1 - left. - Remove the prefix ending at
right - 1. The length isright. - Merge a part of the prefix
arr[0...i]with a part of the suffixarr[j...n-1]wherei <= leftandj >= rightandarr[i] <= arr[j]. The removed length isj - i - 1.
- Remove the suffix starting from
- Initialize
minLengthwith the minimum of the first two cases. - Use a two-pointer technique to find the best merge. Initialize
i = 0andj = right. - Iterate while
i <= leftandj < n:- If
arr[i] <= arr[j], we have a valid merge. UpdateminLength = min(minLength, j - i - 1)and incrementito try a longer prefix. - Otherwise,
arr[i]is too large, so incrementjto find a larger element in the suffix.
- If
- Return the final
minLength.
Walkthrough
A key insight is that the final sorted array will consist of a prefix arr[0...i] and a suffix arr[j...n-1] from the original array, where arr[0...i] and arr[j...n-1] are themselves non-decreasing, and arr[i] <= arr[j]. This implies i must be within the initial non-decreasing prefix of arr, and j must be within the initial non-decreasing suffix of arr.
First, we identify the longest non-decreasing prefix, ending at index left. Then, we find the longest non-decreasing suffix, starting at index right. If left >= right, the array is already sorted.
Otherwise, we have three options:
- Keep the prefix
arr[0...left]and remove the rest. Length to remove:n - 1 - left. - Keep the suffix
arr[right...n-1]and remove the rest. Length to remove:right. - Merge a prefix
arr[0...i](withi <= left) and a suffixarr[j...n-1](withj >= right). We want to findiandjthat minimize the removed lengthj - i - 1, subject toarr[i] <= arr[j]. This can be done efficiently with two pointers, one scanning the prefix part and one scanning the suffix part.
class Solution { public int findLengthOfShortestSubarray(int[] arr) { int n = arr.length; int left = 0; while (left + 1 < n && arr[left] <= arr[left + 1]) { left++; } if (left == n - 1) { return 0; // Array is already sorted } int right = n - 1; while (right > 0 && arr[right - 1] <= arr[right]) { right--; } // Case 1 & 2: Remove suffix or prefix int minLength = Math.min(n - 1 - left, right); // Case 3: Merge prefix and suffix int i = 0; int j = right; while (i <= left && j < n) { if (arr[i] <= arr[j]) { // Found a valid merge, remove arr[i+1...j-1] minLength = Math.min(minLength, j - i - 1); i++; // Try to extend the prefix } else { // arr[i] is too large, need a larger element from the suffix j++; } } return minLength; }}Complexity
Time
O(N). Finding `left` takes O(N), finding `right` takes O(N), and the two-pointer scan also takes O(N) because pointers `i` and `j` only move in one direction across the array. The total complexity is O(N) + O(N) + O(N) = O(N).
Space
O(1), as it only uses a few variables to store pointers and the minimum length, regardless of the input size.
Trade-offs
Pros
Optimal time complexity of O(N).
Optimal space complexity of O(1).
Efficiently handles large inputs within time limits.
Cons
The logic is more complex and less intuitive than brute-force approaches.
Requires careful handling of pointers and edge cases.
Solutions
Solution
class Solution {public int findLengthOfShortestSubarray(int[] arr) { int n = arr.length; int i = 0, j = n - 1; while (i + 1 < n && arr[i] <= arr[i + 1]) { ++i; } while (j - 1 >= 0 && arr[j - 1] <= arr[j]) { --j; } if (i >= j) { return 0; } int ans = Math.min(n - i - 1, j); for (int l = 0; l <= i; ++l) { int r = search(arr, arr[l], j); ans = Math.min(ans, r - l - 1); } return ans; }private int search(int[] arr, int x, int left) { int right = arr.length; while (left < right) { int mid = (left + right) >> 1; if (arr[mid] >= x) { right = mid; } else { left = mid + 1; } } return left; }}class Solution {public: int findLengthOfShortestSubarray(vector<int> &arr) { int n = arr.size(); int i = 0, j = n - 1; while (i + 1 < n && arr[i] <= arr[i + 1]) { ++i; } while (j - 1 >= 0 && arr[j - 1] <= arr[j]) { --j; } if (i >= j) { return 0; } int ans = min(n - 1 - i, j); for (int l = 0; l <= i; ++l) { int r = lower_bound(arr.begin() + j, arr.end(), arr[l]) - arr.begin(); ans = min(ans, r - l - 1); } return ans; }};class Solution: def findLengthOfShortestSubarray(self, arr: List[int]) -> int: n = len(arr) i, j = 0, n - 1 while i + 1 < n and arr[i] <= arr[i + 1]: i += 1 while j - 1 >= 0 and arr[j - 1] <= arr[j]: j -= 1 if i >= j: return 0 ans = min(n - i - 1, j) for l in range(i + 1): r = bisect_left(arr, arr[l], lo=j) ans = min(ans, r - l - 1) return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.