# 1574. 删除最短的子数组使剩余数组有序

### 题目地址（1574. 删除最短的子数组使剩余数组有序）

<https://leetcode-cn.com/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/>

### 题目描述

```
给你一个整数数组 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
解释：数组已经是非递减的了，我们不需要删除任何元素。
示例 4：

输入：arr = [1]
输出：0
 

提示：

1 <= arr.length <= 10^5
0 <= arr[i] <= 10^9

```

### 前置知识

* 双指针
* [滑动窗口](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/thinkings/slide-window)

### 公司

* 暂无

### 思路

首先考虑如果题目不要求必须删除连续的子数组，而是任意的子序列。那么我们可以使用 LIS（最长上升子序列模型）求出 LIS 长度，然后用 n 减去它即可。对于 LIS 模型不熟悉的，可以看下我的[这篇文章](https://lucifer.ren/blog/2020/06/20/LIS/)。

这道题是求极值的，我首先想到的 DP，简单思考了下没啥思路。 而这道题要求我们必须连续，那就考虑滑动窗口。

首先我们将数组分成三部分 A，B，C（A，B，C 都可为空）。由于删除必须连续，因此我们不能只删除 A，C，除此之外可以随便删除。这是题目条件，也是解决问题的关键之一。

不难看出题目的解空间上界是 n - 1，下界是 0，其中 n 为数组长度。

进一步思考。题目的上界其实也可以是 `n - 最长连续非递减子序列`的长度。我们可以扫描一次数组，统计最长的连续非递减的子序列长度即可。

Java 代码：

```java
ans = cnt = 1
for(int i = 1; i < A.length; i++ ) {
    if (A[i] >= A[i - 1]) {
        cnt++
    }
    else {
        ans = max(ans, cnt)
        cnt = 1
    }
}
```

这样 ans 就是 **最长连续非递减子序列** 的长度了。

但是显然这只是上界， 并不是正确解。一个显而易见的反例是：

![](https://p.ipic.vip/2j5gs6.jpg)

如图我们取蓝色部分，而将红色部分删除，答案可能会更小。

实际上，这道题的思路和[11. 盛最多水的容器](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/medium/11.container-with-most-water) 有点类似。只是这道题比较隐蔽，不那么容易想到。因此大家可以先从那道题开始，了解下这个套路。

一个可行的思路是初始化两个指针，一个指向头部，一个指向从尾部起第一个拐点（如上图右边蓝色部分的左端点）。

![](https://p.ipic.vip/490p5b.jpg)

假设左指针为 i 右指针为 j，我们只需要不断右移左指针，左移右指针，并根据 i 和 j 的相对大小更新窗口即可。

值得注意的是，左指针不应该超过右侧第一个拐点，右指针也不应该超过左侧第一个拐点，原因就是前面讲到的**不能只删除 A，C**。 因此左指针移动的过程是单调非递减的，右指针是单调非递增的。这是本题的重中之重。

具体来说：

* 如果 A\[i] <= A\[j]，我们可以选择删除 \[i+1,j-1] 得到一个**候选解**。
* 如果 A\[i] > A\[j]，屋面不仅无法通过删除 \[i+1,j-1] 得到候选解，并且大于 A\[i] 的更不用看了，更加不会满足。又由于上面分析的 A\[i] 移动过程是单调不递减的，因此就没有必要继续移动了。我们可以通过右移左指针来排序所有的 \[i+1, j-1]，\[i+2, j- 1]，.....。

当然这里面还有一些细节，大家需要看代码才能完整领会。强烈建议大家自己写画一个图，然后写一遍，特别需要注意各种边界的判断。

### 关键点

* 画图
* 边界条件的考察（比如+1 -1 等号）

### 代码

代码支持：C++，Python3

Python3 Code:

```python
class Solution:
    def findLengthOfShortestSubarray(self, A: List[int]) -> int:
        n = len(A)
        l, r = 0, n - 1

        while l < n - 1 and A[l] <= A[l + 1]:
            l += 1
        if l == n - 1:
            return 0
        while r > 0 and A[r] >= A[r - 1]:
            r -= 1
        ans = min(r, n - l - 1)
        i = 0
        while i <= l and r < n:
            if A[i] <= A[r]:
                # delete i + 1 ~ r - 1
                ans = min(ans, r - i - 1)
                i += 1
            else:
                # extend the sliding window
                r += 1
        return ans

```

C++ Code:

```cpp
class Solution {
public:
    int findLengthOfShortestSubarray(vector<int>& A) {
        int N = A.size(), left = 0, right = N - 1;
        while (left + 1 < N && A[left] <= A[left + 1]) ++left;
        if (left == A.size() - 1) return 0;
        while (right > left && A[right - 1] <= A[right]) --right;
        int ans = min(N - left - 1, right), i = 0, j = right;
        while (i <= left && j < N) {
            if (A[j] >= A[i]) {
                ans = min(ans, j - i - 1);
                ++i;
            } else ++j;
        }
        return ans;
    }
};
```

**复杂度分析**

* 时间复杂度：$O(N)$，其中 N 为数组长度。
* 空间复杂度：$O(1)$

### 相关题目

* [11. 盛最多水的容器](https://leetcode-solution-leetcode-pp.gitbook.io/leetcode-solution/medium/11.container-with-most-water)

更多题解可以访问我的 LeetCode 题解仓库：<https://github.com/azl397985856/leetcode> 。 目前已经 37K star 啦。

关注公众号力扣加加，努力用清晰直白的语言还原解题思路，并且有大量图解，手把手教你识别套路，高效刷题。

![](https://p.ipic.vip/wfci89.jpg)
