绝对差不超过限制的最长连续子数组 图解题解
这道题到底在问什么
- 输入
- nums=[8,2,4,7], limit=4
- 输出
- 2
- 输入
- nums=[10,1,2,4,7,2], limit=5
- 输出
- 4
最优解:一步一步想明白
- 3记住「maxq 递减管最大、minq 递增管最小;右扩弹尾入队、超限左缩弹首、记最长」,下面每一格都在套它。
- 4这一行是数组。maxq、minq 都从空开始,左指针在 0;右指针一格格往右扩,我们盯着两个队列、窗口 max−min,还有最长长度怎么变。
- 5右指针扩到第 0 位,值 10。入队前先维护单调:maxq 尾部都不比 10 小,不弹;minq 尾部都不比 10 大,不弹。然后把下标 0 入两个队。
- 6读队首:窗口最大是 10(下标 0)、最小是 10(下标 0),差 0 不超过 limit 5,这段合法。长度 0−0+1=1,比之前长,最长刷新成 1。
- 7右边再纳入第 1 位,值 1。入队前先维护单调:maxq 尾部都不比 1 小,不弹;minq 尾部比 1 大的也当不了最小,弹掉。然后把下标 1 入两个队。
- 8读队首:最大 10、最小 1,差 9 已经超过 limit 5(标红)。这段不合法,要把左指针往右收,直到差重新合法。
- 9左指针从 0 右移到 1:谁的队首下标正好滑出了窗口,就把它从队首弹掉。现在 max−min=0 ≤ 5,窗口 [1, 1] 合法,长度 1,最长仍是 1。
- 10右指针推进到第 2 位,值 2。入队前先维护单调:maxq 尾部那些比 2 小的值再也当不了最大,弹掉;minq 尾部都不比 2 大,不弹。然后把下标 2 入两个队。
- 11读队首:窗口最大是 2(下标 2)、最小是 1(下标 1),差 1 不超过 limit 5,这段合法。长度 2−1+1=2,比之前长,最长刷新成 2。
- 12窗口右扩到第 3 位,值 4。入队前先维护单调:maxq 尾部那些比 4 小的值再也当不了最大,弹掉;minq 尾部都不比 4 大,不弹。然后把下标 3 入两个队。
- 13读队首:窗口最大是 4(下标 3)、最小是 1(下标 1),差 3 不超过 limit 5,这段合法。长度 3−1+1=3,比之前长,最长刷新成 3。
- 14右端来到第 4 位,值 7。入队前先维护单调:maxq 尾部那些比 7 小的值再也当不了最大,弹掉;minq 尾部都不比 7 大,不弹。然后把下标 4 入两个队。
- 15读队首:最大 7、最小 1,差 6 已经超过 limit 5(标红)。这段不合法,要把左指针往右收,直到差重新合法。
- 16左指针从 1 右移到 2:谁的队首下标正好滑出了窗口,就把它从队首弹掉。现在 max−min=5 ≤ 5,窗口 [2, 4] 合法,长度 3,最长仍是 3。
- 17继续右扩到第 5 位,值 2。入队前先维护单调:maxq 尾部都不比 2 小,不弹;minq 尾部比 2 大的也当不了最小,弹掉。然后把下标 5 入两个队。
- 18读队首:窗口最大是 7(下标 4)、最小是 2(下标 2),差 5 不超过 limit 5,这段合法。长度 5−2+1=4,比之前长,最长刷新成 4。
- 19扫完全程,最长的合法窗口长度是 4。回头看:每个下标进出两个单调队列各一次、左右指针各只前进一遍,所以是 O(n)。
⚠️ 容易写错的地方
✗ 错:每次重新扫窗口求 max/min
✓ 对:两个单调队列分别 O(1) 给最值
每窗口重扫是 O(nk),单调队列均摊 O(1)、整体 O(n)
✗ 错:队列里存值
✓ 对:存下标
左缩时要判断队首是否滑出窗口(下标 == left),只存值判不了过期
✗ 错:左缩时无脑弹队首
✓ 对:只在队首下标 == left 时才弹
当前最值可能在窗口中间,它没滑出就不能弹,否则会丢掉有效最值
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import deque
class Solution:
def longestSubarray(self, nums: List[int], limit: int) -> int:
maxq, minq = deque(), deque()
left = ans = 0
for right, x in enumerate(nums):
while maxq and nums[maxq[-1]] < x:
maxq.pop()
while minq and nums[minq[-1]] > x:
minq.pop()
maxq.append(right)
minq.append(right)
while nums[maxq[0]] - nums[minq[0]] > limit:
if maxq[0] == left:
maxq.popleft()
if minq[0] == left:
minq.popleft()
left += 1
ans = max(ans, right - left + 1)
return ansC++
#include <algorithm>
#include <deque>
#include <vector>
using namespace std;
class Solution {
public:
int longestSubarray(vector<int>& nums, int limit) {
deque<int> maxq, minq;
int left = 0, ans = 0;
for (int right = 0; right < (int)nums.size(); ++right) {
while (!maxq.empty() && nums[maxq.back()] < nums[right]) maxq.pop_back();
while (!minq.empty() && nums[minq.back()] > nums[right]) minq.pop_back();
maxq.push_back(right); minq.push_back(right);
while (nums[maxq.front()] - nums[minq.front()] > limit) {
if (maxq.front() == left) maxq.pop_front();
if (minq.front() == left) minq.pop_front();
left++;
}
ans = max(ans, right - left + 1);
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int longestSubarray(int[] nums, int limit) {
Deque<Integer> maxq = new ArrayDeque<>(), minq = new ArrayDeque<>();
int left = 0, ans = 0;
for (int right = 0; right < nums.length; right++) {
while (!maxq.isEmpty() && nums[maxq.peekLast()] < nums[right]) maxq.pollLast();
while (!minq.isEmpty() && nums[minq.peekLast()] > nums[right]) minq.pollLast();
maxq.offerLast(right); minq.offerLast(right);
while (nums[maxq.peekFirst()] - nums[minq.peekFirst()] > limit) {
if (maxq.peekFirst() == left) maxq.pollFirst();
if (minq.peekFirst() == left) minq.pollFirst();
left++;
}
ans = Math.max(ans, right - left + 1);
}
return ans;
}
}复杂度
时间
O(n)
每个下标进出 maxq、minq 各一次,左右指针各只前进一遍
空间
O(n)
两个双端队列最坏各存 O(n) 个下标(如严格递增/递减数组)
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 绝对差不超过限制的最长连续子数组 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么 maxq 要递减、minq 要递增?+
我们要的是窗口最大值和最小值。maxq 维护成「下标对应值递减」,队首自然是最大值;新来的值若比尾部大,尾部那些更小的值在它存在期间永远轮不到当最大,可以安全弹掉。minq 对称,维护成递增、队首是最小值。两个队列各管一头,O(1) 取窗口最值。
除了单调队列,还有别的解法吗?+
可以用一个有序多重集(如 C++ 的 multiset、Java 的 TreeMap)维护窗口内所有值,取最大最小是 begin/rbegin,超限就删掉左端值。它写起来更短,但每步是 O(log n),整体 O(n log n);单调队列均摊 O(1)、整体 O(n),更快。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 绝对差不超过限制的最长连续子数组 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。