leetcode:1574. 删除最短的子数组使剩余数组有序
题目
给你一个整数数组 arr
,请你删除一个子数组(可以为空),使得 arr
中剩下的元素是 非递减 的。
一个子数组指的是原数组中连续的一个子序列。
请你返回满足题目要求的最短子数组的长度。
示例:
输入:arr = [1,2,3,10,4,2,3,5]
输出:3
解释:我们需要删除的最短子数组是 [10,4,2] ,长度为 3 。剩余元素形成非递减数组 [1,2,3,3,5] 。
另一个正确的解为删除子数组 [3,10,4] 。
输入:arr = [5,4,3,2,1]
输出:4
解释:由于数组是严格递减的,我们只能保留一个元素。所以我们需要删除长度为 4 的子数组,要么删除 [5,4,3,2],要么删除 [4,3,2,1]。
输入:arr = [1,2,3]
输出:0
解释:数组已经是非递减的了,我们不需要删除任何元素。
输入:arr = [1]
输出:0
解答 & 代码
同向双指针(滑动窗口)
class Solution {
public:
int findLengthOfShortestSubarray(vector<int>& arr) {
int len = arr.size();
// 定位到左边非递减序列的终点,使得 [0, leftEnd] 为非递减序列
int leftEnd = 1;
while(leftEnd < len && arr[leftEnd] >= arr[leftEnd - 1])
++leftEnd;
if(leftEnd == len) // 如果 arr 数组本身有序,则无需删除,因此直接返回 0
return 0;
--leftEnd;
// 定位到右边非递减序列的起点,使得 [rightStart, len) 为非递减序列
int rightStart = len - 2;
while(rightStart >= 0 && arr[rightStart] <= arr[rightStart + 1])
--rightStart;
++rightStart;
// case 1:只保留左边非递减序列,则需要删除其右边剩余的长为 len - leftEnd - 1 的子数组
// case 2:只保留右边非递减序列,则需要删除其左边剩余的长为 rightStart 的子数组
// result 初始取两者最小值
int result = min(len - leftEnd - 1, rightStart);
// case 3:删除中间的某段连续子数组(窗口)。要获取最短的窗口长度
// 同向双指针(滑动窗口),分别在左边非递减序列、右边非递减序列滑动
int left = 0;
int right = rightStart;
while(left <= leftEnd && right < len)
{
// [0, left] 和 [right, len-1] 可以连起来形成非递减序列,
// 说明删掉窗口 [left + 1, right - 1] 即可得到非递减序列(窗口合法)
if(arr[left] <= arr[right])
{
result = min(result, right - left - 1); // 取窗口长度更新 reuslt
++left; // 滑动 left 指针
}
// arr[left] > arr[right],而 left 右边的数只会更大,
// 因此需要右移 right 找到比 arr[left] 大的位置(窗口不合法)
else
++right; // 滑动 right 指针,使得 arr[right] 增大
}
return result;
}
};
复杂度分析:设数组 arr
长为 N
- 时间复杂度 O(N):
- 空间复杂度 O(1):
执行结果:
执行结果:通过
执行用时:104 ms, 在所有 C++ 提交中击败了 44.37% 的用户
内存消耗:65.2 MB, 在所有 C++ 提交中击败了 69.37% 的用户