最大值减去最小值小于等于aim的子数组数量(单调队列(最大值和最小值更新结构))

注意: 子数组必须是下标连续的,而且i ~ i自己也算一个子数组。
解析
这个题目也是使用单调队列(窗口内更新最大值和最小值)的结构来做,如果不懂单调队列先看这个博客。
先准备两个双端队列,分别是最大值更新结构和最小值更新结构:
- 先生成两个双端队列
qmax和qmin,当子数组为arr[L...R]时,qmax维护了窗口子数组arr[L...R]的最大值更新结构,qmin维护了窗口子数组arr[L....R]的最小值更新结构; - 当子数组
arr[L....R]向右扩一个位置变成arr[L....R+1]时,qmax和qmin可以在O(1)时间内完成更新;并且可以在O(1)时间内得到窗口的最大值和最小值; - 当子数组
arr[L....R]左边缩一个位置变成arr[L+1....R]是,qmax和qmin可以在O(1)时间内完成更新;并且可以在O(1)时间内得到窗口的最大值和最小值;
然后,我们需要证明两个结论:

下面看具体过程:
- 找到一个
L,此时令R不断向右移动,表示arr[L...R]一直向右扩大,并不断更新qmax和qmin的结构,保证qmax和qmin始终维持动态窗口最大值和最小值的更新结构; - 一旦出现
arr[L....R]中出现max - min > aim的情况,R向右扩的过程停止(上面证明结论的第二条),此时arr[L....R-1],arr[L....R-2],arr[L....R-3]....arr[L,L]都是满足条件的子数组(上面证明结论的第一条)。也就是说,所以必须以arr[L]开头的子数组,总共有R - L个,res += R-L; - 然后要注意两个队列中的过期的元素,也就是说队头的元素考虑完了之后要弹出;
- 然后,继续考虑下一个
L,直到循环结束;
由于L,R的值是一直增加的(不会减小),且所有的下标最多进qmax、qmin一次,出qmax、qmin一次,时间复杂度为O(n)。
1 | static int getNum2(int[] arr, int aim) { |
完整的测试代码如下(包括使用O(n^3)方法来测试我们的O(n)方法):
1 | import java.util.LinkedList; |