绝对差不超过限制的最长连续子数组
给你一个整数数组 `nums` ,和一个表示限制的整数 `limit`,请你返回最长连续子数组的长度,该子数组中的任意两个元素之间的绝对差必须小于或者等于 `limit`。
示例 1:
输入:nums = [8,2,4,7], limit = 4 输出:2 解释:所有子数组如下: [8] 最大绝对差 |8-8| = 0 4. [8,2,4] 最大绝对差 |8-2| = 6 > 4. [8,2,4,7] 最大绝对差 |8-2| = 6 > 4. [2] 最大绝对差 |2-2| = 0 4. [4] 最大绝对差 |4-4| = 0
示例 2:
输入:nums = [10,1,2,4,7,2], limit = 5 输出:4 解释:满足练习意的最长子数组是 [2,4,7,2],其最大绝对差 |2-7| = 5
示例 3:
输入:nums = [4,2,2,2,4,4,2,2], limit = 0 输出:3
提示:
- `1 5`
- `1 9`
- `0 9`
python
from collections import deque
from typing import List
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
# max_d: 单调递减队列,队首是当前窗口最大值下标
# min_d: 单调递增队列,队首是当前窗口最小值下标
max_d = deque()
min_d = deque()
left = 0
ans = 0
for right, x in enumerate(nums):
# 维护最大值队列:递减
while max_d and nums[max_d[-1]] < x:
max_d.pop()
max_d.append(right)
# 维护最小值队列:递增
while min_d and nums[min_d[-1]] > x:
min_d.pop()
min_d.append(right)
# 如果窗口不合法,就不断右移左边界
while nums[max_d[0]] - nums[min_d[0]] > limit:
if max_d[0] == left:
max_d.popleft()
if min_d[0] == left:
min_d.popleft()
left += 1
ans = max(ans, right - left + 1)
return ans这练习的核心不是“任意两数都要比较”,而是先把条件转化掉。
## 1. 关键等价变形 对于一个子数组来说,任意两个元素的最大绝对差,实际上就是: - 最大值 `max` - 最小值 `min` - 只要满足 `max - min <= limit`,就说明该窗口内任意两数差都不超过 `limit`
所以任务本质变成: 找一个最长连续子数组,使得窗口内 `最大值 - 最小值 <= limit`。
## 2. 为什么用双端队列 我们要在滑动窗口中快速维护: - 当前窗口最大值 - 当前窗口最小值
如果每次都重新扫描窗口,复杂度会退化到 `O(n^2)`。 因此使用两个单调队列: - `max_d`:单调递减,队首永远是当前窗口最大值下标 - `min_d`:单调递增,队首永远是当前窗口最小值下标
每个元素最多进队、出队一次,所以整体是 `O(n)`。
## 3. 方法流程 双指针 `left/right` 扫描: 1. 右端点加入窗口时,更新两个单调队列。 2. 如果 `nums[max_d[0]] - nums[min_d[0]] > limit`,说明窗口非法。 3. 这时移动 `left` 缩小窗口,并且如果左端点刚好是队首元素,就把它弹出。 4. 每次窗口合法后,更新答案。
## 4. 正确性要点 高质量答案需要说明: - 队列里存的是下标,不是值,这样才能判断元素是否已经滑出窗口。 - 单调性保证了队首一定是窗口内最大/最小值。 - 当窗口非法时,只需要移动左边界直到合法,因为右边界已固定。 - 每个元素只会被加入和删除一次,因此线性复杂度成立。
## 5. 复杂度 - 时间复杂度:`O(n)` - 空间复杂度:`O(n)` 队列最坏情况下会存下整个窗口中的下标。
## 6. 常见错误 1. 只维护一个最大值和一个最小值变量 - 窗口收缩后,这两个值可能已经失效,无法正确更新。 2. 队列存值而不是下标 - 遇到重复元素时,无法判断哪个元素过期。 3. 只在右端点加入时检查一次,不持续收缩 - 可能导致窗口一直非法。 4. 把条件误写成“相邻元素差 <= limit” - 任务要求的是窗口内任意两元素的差。 5. 忽略空窗口/重复元素/limit=0 - 这些都是常见边界情况。
## 7. 出练习者可能追问什么 - 为什么 `max - min <= limit` 就足够? - 为什么双端队列能做到 `O(n)`? - 如果不用双端队列,能否用堆?复杂度会怎样? - 如果数组长度很大,如何证明每个元素最多进出队一次? - 能否写出“存值版”和“存下标版”的区别?
- 为什么最大差只需看窗口最大值和最小值
- 如果用堆实现,如何做延迟删除
- 为什么队列里要存下标而不是数值