题目描述
给你一个整数数组nums
,和一个表示限制的整数 limit
,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于limit
。
如果不存在满足条件的子数组,则返回0
。
示例 1:
输入:nums = [8,2,4,7], limit = 4
输出:2
解题思路
-
left
、right
双指针,指向遍历到的数组下标。nums
数组的闭区间下标[left, right]
为双指针表示的子数组。- 令
max_data
为 子区间[left, right]
中对应的最大值 - 令
min_data
为 子区间[left, right]
中对应的最小值
- 令
-
若
max_data
和min_data
的差 小于等于limit
,说明该子数组满足条件,并将right
右移一位。- 否则
- 说明子数组不满足条件,即
max_data - min_data > limit
- 并且此时
nums[right]
对应的值一定是 最大值 或者最小值(反证法 易证),此时需要将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)。