双指针+双单调队列或堆_5402_绝对差不超过限制的最长连续子数组

题目描述

给你一个整数数组nums ,和一个表示限制的整数 limit,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于limit

如果不存在满足条件的子数组,则返回0

示例 1:

输入:nums = [8,2,4,7], limit = 4
输出:2 

解题思路

  • leftright 双指针,指向遍历到的数组下标。nums 数组的闭区间下标 [left, right]为双指针表示的子数组。

    • max_data 为 子区间 [left, right] 中对应的最大值
    • min_data 为 子区间 [left, right] 中对应的最小值
  • max_datamin_data 的差 小于等于 limit,说明该子数组满足条件,并将right右移一位。

    • 否则
    1. 说明子数组不满足条件,即 max_data - min_data > limit
    2. 并且此时 nums[right] 对应的值一定是 最大值 或者最小值(反证法 易证),此时需要将 left 右移,移到 另一个最值的右边。如下图所示:
      左指针left需要移动的情景

    注意: left 右移时可以一位一位的右移,也可以一次性右移至最值右边的位置,这样更高效。

  • 对应代码如下:

暴力法

class Solution:
    def longestSubarray(self, nums, limit):
        i, j = 0, 1
        max_len, max_data, min_data = 1, nums[0], nums[0]
        while j < len(nums):
            max_data = max(nums[i:j+1])
            min_data = min(nums[i:j+1])
            if max_data - min_data > limit:
                i += 1
            else:
                max_len = max(max_len, len(nums[i: j+1]))
                j += 1
        return max_len
  • 时间复杂度: O ( n 2 ) O(n^2) O(n2),外层循环的遍历时 O(n) 的时间复杂度,内层寻找最大值\最小值 max(nums[i:j+1])min(nums[i:j+1]) 也是 O(n)的时间复杂度。
  • 空间复杂度:O(1),只用到了常数的空间。

可以借助一些数据结构,使得内层查找最大最小值的时间复杂度降为O(logn) – 红黑树或者堆,或 O(1) 单调队列

借助堆 – 双指针 + 大顶堆 和 小顶堆

  • python 没有大顶堆,这里可以取负值来表示,最小的负值 对应 最大的正值
class Solution:
    def longestSubarray(self, nums: List[int], limit: int) -> int:
        from heapq import heappop, heappush
        max_ = []
        min_ = []
        res = 0
        l = 0
        for r, num in enumerate(nums):
            # 大根堆 max_
            heappush(max_, [-num, r])
            # 小根堆 min_
            heappush(min_, [num, r])
            # l 为左指针位置
            while -max_[0][0] - min_[0][0] > limit:
                # 条件判断需要max,min[0][0]存的索引不在 l 左侧
                # 删除不在 l 右侧的元素
                while min_[0][1] <= l:
                    heappop(min_)
                while max_[0][1] <= l:
                    heappop(max_)
                # 移动 l
                l += 1
            # 找到最长的符合要求的窗口长度
            if r - l + 1 > res:
                res = r - l + 1
        return res
  • 时间复杂度: O ( n l o g n ) O(nlogn) O(nlogn),外层循环的遍历时 O(n) 的时间复杂度,内层寻找最大值\最小值是 O(1)的时间复杂度,但是建堆的时间复杂度是 O ( l o g n ) O(logn) O(logn)
  • 空间复杂度:O(n),建堆是O(n)的空间。

借助堆 – 双指针 + 双单调队列

class Solution:
    def longestSubarray(self, nums, limit):
        """
        用两个单调队列分别维护从left到right之间的最大值和最小值
        保证可以以O(1)取得区间里的最大最小值
        """
        from collections import deque
        min_q, max_q = deque(), deque()
        left = 0
        ans = 0

        for right, num in enumerate(nums):
            while min_q and min_q[-1] > num:
                min_q.pop()  # min_q 单调不减
            min_q.append(num)

            while max_q and max_q[-1] < num:
                max_q.pop()  # max_q 单调不增
            max_q.append(num)

            # print("min_q", min_q)
            # print("max_q", max_q)

            while left < len(nums) and ((min_q and abs(num - min_q[0]) > limit) 
                    or (max_q and abs(num - max_q[0]) > limit)):
                # 注意:当最大值-最小值 > limit 时,这时候 right一定是最大值的下标 或 是最小值的下标
                if min_q and nums[left] == min_q[0]:  # left递增 找另一个最值的下标
                    min_q.popleft()
                if max_q and nums[left] == max_q[0]:
                    max_q.popleft()
                left += 1  # left递增
            ans = max(ans, right+1-left)
        return ans
  • 时间复杂度: O ( n ) O(n) O(n),外层循环的遍历时 O(n) 的时间复杂度,内层寻找最大值\最小值 是 O(1)的时间复杂度。
  • 空间复杂度:O(1)。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值