Medium
题目描述
给你一个整数数组 arr,请你删除一个子数组(可以为空),使得 arr 中剩下的元素是 非递减 的。
返回满足题意的最短子数组的长度。
子数组 定义为数组中任意个连续的元素。
示例 1:
输入:arr = [1,2,3,10,4,2,3,5]
输出:3
解释:我们需要删除的最短子数组是 [10,4,2] ,长度为 3 。剩余元素为 [1,2,3,3,5] 。
另一个正确的解是删除子数组 [3,10,4] 。
示例 2:
输入:arr = [5,4,3,2,1]
输出:4
解释:由于数组是严格递减的,我们只能保留一个元素。所以我们需要删除长度为 4 的子数组,要么是 [5,4,3,2],要么是 [4,3,2,1]。
示例 3:
输入:arr = [1,2,3]
输出:0
解释:数组已经是非递减的了。我们不需要删除任何元素。
提示:
1 <= arr.length <= 10^50 <= arr[i] <= 10^9
解题思路
这道题的核心思想是:删除子数组后,剩余部分是左边一个非递减的前缀和右边一个非递减的后缀的拼接。
解题思路:
- 寻找左边界:从左开始找到最长的非递减前缀,记录边界
left - 寻找右边界:从右开始找到最长的非递减后缀,记录边界
right - 三种情况:
- 保留左前缀:删除
[left+1, n-1] - 保留右后缀:删除
[0, right-1] - 保留左前缀+右后缀:使用双指针找到能连接的最佳组合
- 保留左前缀:删除
对于第三种情况,我们需要确保左前缀的最后一个元素 ≤ 右后缀的第一个元素。通过双指针遍历所有可能的组合,找到删除子数组长度的最小值。
时间复杂度: O(n),每个元素最多被访问常数次 空间复杂度: O(1),只使用常量额外空间
这种方法避免了复杂的分情况讨论,通过统一的双指针框架解决问题。
代码实现
class Solution {
public:
int findLengthOfShortestSubarray(vector<int>& arr) {
int n = arr.size();
// 找到左边最长的非递减序列
int left = 0;
while (left + 1 < n && arr[left] <= arr[left + 1]) {
left++;
}
// 如果整个数组已经是非递减的
if (left == n - 1) return 0;
// 找到右边最长的非递减序列
int right = n - 1;
while (right > 0 && arr[right - 1] <= arr[right]) {
right--;
}
// 三种情况的最小值
int result = min(n - left - 1, right);
// 尝试保留左前缀和右后缀
int i = 0, j = right;
while (i <= left && j < n) {
if (arr[i] <= arr[j]) {
result = min(result, j - i - 1);
i++;
} else {
j++;
}
}
return result;
}
};
class Solution:
def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
n = len(arr)
# 找到左边最长的非递减序列
left = 0
while left + 1 < n and arr[left] <= arr[left + 1]:
left += 1
# 如果整个数组已经是非递减的
if left == n - 1:
return 0
# 找到右边最长的非递减序列
right = n - 1
while right > 0 and arr[right - 1] <= arr[right]:
right -= 1
# 三种情况的最小值
result = min(n - left - 1, right)
# 尝试保留左前缀和右后缀
i, j = 0, right
while i <= left and j < n:
if arr[i] <= arr[j]:
result = min(result, j - i - 1)
i += 1
else:
j += 1
return result
public 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;
// 找到右边最长的非递减序列
int right = n - 1;
while (right > 0 && arr[right - 1] <= arr[right]) {
right--;
}
// 三种情况的最小值
int result = Math.Min(n - left - 1, right);
// 尝试保留左前缀和右后缀
int i = 0, j = right;
while (i <= left && j < n) {
if (arr[i] <= arr[j]) {
result = Math.Min(result, j - i - 1);
i++;
} else {
j++;
}
}
return result;
}
}
var findLengthOfShortestSubarray = function(arr) {
const n = arr.length;
// Find the longest non-decreasing prefix
let left = 0;
while (left < n - 1 && arr[left] <= arr[left + 1]) {
left++;
}
// If the entire array is already sorted
if (left === n - 1) return 0;
// Find the longest non-decreasing suffix
let right = n - 1;
while (right > 0 && arr[right - 1] <= arr[right]) {
right--;
}
// Option 1: Remove everything after the prefix
let result = right;
// Option 2: Remove everything before the suffix
result = Math.min(result, left + 1);
// Option 3: Remove elements in the middle by merging prefix and suffix
let i = 0, j = right;
while (i <= left && j < n) {
if (arr[i] <= arr[j]) {
result = Math.min(result, j - i - 1);
i++;
} else {
j++;
}
}
return result;
};
复杂度分析
| 复杂度类型 | 大小 |
|---|---|
| 时间复杂度 | O(n) |
| 空间复杂度 | O(1) |