数组(四)-- LC[1574] 删除最短的子数组使剩余数组有序

1 删除最短的子数组使剩余数组有序

1.1 题目描述

        题目链接:https://leetcode.cn/problems/shortest-subarray-to-be-removed-to-make-array-sorted/description/

1.2 滑动窗口

1. 枚举左端点,移动右端点

        核心思路:枚举 left \textit{left} left,增大 right \textit{right} right 直到 arr [ left ] ≤ arr [ right ] \textit{arr}[\textit{left}]\le\textit{arr}[\textit{right}] arr[left]arr[right],此时更新子数组长度的最小值。

解疑答惑:

        问:为什么枚举一个新的 left \textit{left} left 时, right \textit{right} right 不会往左移?或者说,是否需要再次枚举之前枚举过的 arr [ right ] \textit{arr}[\textit{right}] arr[right]

        答:在向右移动时,由于 arr [ left ] \textit{arr}[\textit{left}] arr[left] arr [ right ] \textit{arr}[\textit{right}] arr[right] 都是非递减的,所以 right \textit{right} right 左侧之前枚举过的元素必然小于 arr [ left ] \textit{arr}[\textit{left}] arr[left],无需再次枚举。这也是本题可以使用同向双指针(不定长滑动窗口)的前提。

        问:在计算子数组长度时,我经常分不清下标是否要 +1 或 −1,请问如何解决?

        答:第一,时刻把握住 left \textit{left} left right \textit{right} right 的含义,对于本题来说是开区间 ( left , right ) (\textit{left},\textit{right}) (left,right),这两个指针指向的元素不能删除。第二,可以代入一些数据来验证,比如代入 left = 1 , right = 3 \textit{left}=1, \textit{right}=3 left=1,right=3,此时只需要删除一个 arr [ 2 ] \textit{arr}[2] arr[2],所以公式 right − left − 1 \textit{right}-\textit{left}-1 rightleft1 才是符合要求的。

        问:为什么不用判断 left < right \textit{left}<\textit{right} left<right,难道不会出现 left ≥ right \textit{left}\ge\textit{right} leftright 的情况吗?

        答:由于提前判断了 arr \textit{arr} arr 是非递减数组的情况,后面的循环 left \textit{left} left 必定小于 right \textit{right} right。反证:如果某个时刻 left \textit{left} left 达到了 right \textit{right} right,就说明整个数组是有序的,但这种情况已经提前判断了。

        问:能不能先把 left \textit{left} left 的最大值算出来,然后再去枚举 left \textit{left} left right \textit{right} right

        答:可以。根据对称性,这种做法和先算 right \textit{right} right 的最小值的做法是一样的,只不过枚举的顺序相反而已。

class Solution:
    def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
        n = len(arr)
        right = n - 1
        while right and arr[right - 1] <= arr[right]:
            right -= 1
        if right == 0:  # arr 已经是非递减数组
            return 0
        # 此时 arr[right-1] > arr[right]
        ans = right  # 删除 arr[:right]
        left = 0  # 枚举 left
        while left == 0 or arr[left - 1] <= arr[left]:
            while right < n and arr[right] < arr[left]:
                right += 1
            # 此时 arr[left] <= arr[right],删除 arr[left+1:right]
            ans = min(ans, right - left - 1)
            left += 1
        return ans

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n nums \textit{nums} nums 的长度。虽然写了个二重循环,但是内层循环中对 right \textit{right} right 加一的总执行次数不会超过 n n n 次,所以总的时间复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1),仅用到若干额外变量。

2. 枚举右端点,移动左端点

        核心思路:枚举 right \textit{right} right,增大 left \textit{left} left 直到 arr [ left ] > arr [ right ] \textit{arr}[\textit{left}]>\textit{arr}[\textit{right}] arr[left]>arr[right]。在增大过程中去更新子数组长度的最小值。

        问:为什么枚举一个新的 right \textit{right} right 时, left \textit{left} left 不会往左移?或者说,是否需要再次枚举之前枚举过的 arr [ left ] \textit{arr}[\textit{left}] arr[left]

        答:在向右移动时,由于 arr [ left ] \textit{arr}[\textit{left}] arr[left] arr [ right ] \textit{arr}[\textit{right}] arr[right] 都是非递减的,所以 left \textit{left} left 左侧之前枚举过的元素必然小于等于 arr [ right ] \textit{arr}[\textit{right}] arr[right],由于这样的子数组长度更长,无需再次枚举。这也是本题可以使用同向双指针(不定长滑动窗口)的前提。

        问:为什么循环一定会结束?

        答:代码中提前判断了 arr \textit{arr} arr 已经是非递减数组的情况,所以后面的循环一定存在 left \textit{left} left,使得 arr [ left ] > arr [ left + 1 ] \textit{arr}[\textit{left}]>\textit{arr}[\textit{left}+1] arr[left]>arr[left+1] 成立。

注:最坏情况下,当 right = n \textit{right}=n right=n 时才会去移动 left \textit{left} left

class Solution:
    def findLengthOfShortestSubarray(self, arr: List[int]) -> int:
        n = len(arr)
        right = n - 1
        while right and arr[right - 1] <= arr[right]:
            right -= 1
        if right == 0:  # arr 已经是非递减数组
            return 0
        # 此时 arr[right-1] > arr[right]
        ans = right  # 删除 arr[:right]
        left = 0
        while True:  # 枚举 right
            while right == n or arr[left] <= arr[right]:
                ans = min(ans, right - left - 1)  # 删除 arr[left+1:right]
                if arr[left] > arr[left + 1]:
                    return ans
                left += 1
            right += 1

复杂度分析

  • 时间复杂度: O ( n ) O(n) O(n),其中 n n n nums \textit{nums} nums 的长度。虽然写了个二重循环,但是内层循环中对 left \textit{left} left 加一的总执行次数不会超过 n n n 次,所以总的时间复杂度为 O ( n ) O(n) O(n)
  • 空间复杂度: O ( 1 ) O(1) O(1),仅用到若干额外变量。

参考

猜你喜欢

转载自blog.csdn.net/xq151750111/article/details/129780601