题目
给定数组arr和整数num,保证max(arr[i..j]) - min(arr[i..j]) <=num,其中max(arr[i..j])表示子数组 arr[i..j]中的最大值,min(arr[i..j])表示子数组arr[i..j]中的最小值。假定数组长为N,实现时间复杂度O(N)的解法。
分析
有两个重要逻辑:
1、如果子数组arr[i,j]满足条件,则arr[i,j]中的每一个子数组都满足条件
2、如果子数组arr[i,j]不满足条件,则所有包含arr[i,j]的子数组都不满足条件
这道题目本质上需要计算每个子数组的最大值与最小值的差,判断是否符合要求。所以需要设计一个快速获得最大值和最小值的方法。由于子数组中的数字下标是连续的,可以考虑队列和栈之类的结构来实现。参考“生成窗口最大值数组”,可以用双向队列来实现快速获取最大值和最小值,时间复杂度为O(1)。
于是整个算法的流程为:
(1)生成两个双端队列max和min,利用res记录满足条件的子数组的数量;
(2)令j不断右移,不断更新max和min来判断当前子数组是否满足条件。一旦出现arr[i,j]不满足的情况出现,则j停止向右扩的过程,此时arr[i,j-1],arr[i,j-2],……,arr[i,i]一定均满足条件。于是令res=j-i;
(3)令i右移一个位置,对max和min进行更新,max和min从原来的arr[i,j]窗口更新成arr[i+1,j]窗口的最大值和最小值结构。重复步骤2
代码
public class GetNum {
public int getNum(int[] arr, int num) {
if(arr == null || arr.length == 0) {
return 0;
}
LinkedList<Integer> max = new LinkedList<>();
LinkedList<Integer> min = new LinkedList<>();
int i = 0;
int j = 0;
int res = 0;
while (i < arr.length) {
while (j < arr.length) {
while (!max.isEmpty() && arr[j] >= arr[max.peekLast()]) {
max.pollLast();
}
max.offerLast(j);
while (!min.isEmpty() && arr[j] <= arr[min.peekLast()]) {
min.pollLast();
}
min.offerLast(j);
if(arr[max.peekFirst()] - arr[min.peekFirst()] > num) {
break;
}
j++;
}
if(max.peekFirst() == i) {
max.pollFirst();
}
if(min.peekFirst() == i) {
min.pollFirst();
}
System.out.println(i+" " + j);
res += j - i;
i++;
}
return res;
}
}