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 <= 105
0 <= arr[i] <= 109
This problem asks to find the shortest subarray to remove from an integer array to make the remaining array non-decreasing. We'll explore two solutions: one using binary search and another using a two-pointer approach. Both solutions leverage the observation that we only need to consider removing a subarray that disrupts the non-decreasing order.
Core Idea:
The solutions efficiently identify the longest non-decreasing prefix and suffix of the input array. The subarray to be removed lies between these two portions. We then iterate through possibilities, minimizing the length of the subarray removed.
Solution 1: Two Pointers + Binary Search
Find Longest Non-decreasing Prefix and Suffix:
i
and j
, to find the indices marking the end of the longest non-decreasing prefix and the beginning of the longest non-decreasing suffix, respectively.Handle Trivial Cases:
i >= j
, the array is already non-decreasing, and we return 0.Initial Answer:
n - i - 1
) and the length of the remaining array before the suffix (j
).Iterate and Refine:
l
from 0 to i
). For each l
, we use binary search (bisect_left
in Python, lower_bound
in C++, sort.SearchInts
in Go, search
in Java) to find the smallest index r
in the suffix such that arr[r] >= arr[l]
. This efficiently finds the rightmost index that maintains the non-decreasing property when removing arr[l+1...r-1]
.r - l - 1
, and we update the answer (ans
) if this length is smaller.Time Complexity Analysis (Solution 1):
Space Complexity Analysis (Solution 1):
Solution 2: Two Pointers (Optimized)
This solution improves upon the previous one by replacing binary search with a linear scan using two pointers.
Steps 1-3: Same as Solution 1.
Linear Scan for Optimal Removal:
l
from 0 to i
). For each l
, a second pointer r
starts at j
. It moves to the right until it finds an element arr[r]
that is greater than or equal to arr[l]
. This avoids the logarithmic complexity of binary search.Time Complexity Analysis (Solution 2):
Space Complexity Analysis (Solution 2):
Code Examples (Python - Solution 2):
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)
r = j
for l in range(i + 1):
while r < n and arr[r] < arr[l]: # Linear scan instead of binary search
r += 1
ans = min(ans, r - l - 1)
return ans
The other languages' code would follow similar logic. Remember to adjust the binary search or linear scan according to the chosen language's standard library functions. Solution 2 is generally faster in practice, despite its theoretically worse worst-case time complexity. Solution 1 provides a guaranteed O(n log n) time complexity, making it slightly more predictable in terms of performance. The choice depends on the prioritization of theoretical vs. practical efficiency.