题目
给定数组arr和整数num,共返回有多少个子数组满足如下情况:
max(arr[i..j])-min(arr[i..j])<=num
max表示子数组arr[i..j]中的最大值,min表示最小值
要求
如果数组长度为N,请实现时间复杂度为O(N)的解法
思路
普通解法:双重for循环寻找所有的子数组,在循环内部,对每一个子数组遍历找到其中的最小值和最大值,时间复杂度为O(N),总体的时间复杂度为O(N^3).
最优解: 时间复杂度O(N),额外空间复杂度O(N)
数据结构:双端队列qmax和qmin,可参考之前的“生成窗口最大值数组”文章。
最优解步骤:
明确两个结论:
- 如果子数组arr[i..j]满足要求,即max(arr[i..j])-min(arr[i..j])<=num,则子数组arr[i..j]中的每一个子数组都满足条件
- 如果子数组arr[i..j]不满足要求,则所有包含子数组的子数组,都不满足条件
- 生成两个双端队列qmax,qmin,两个整形变量i,j表示数组范围即arr[i..j]。整形变量res,表示所有满足条件的子数组数量。
- j不断右移,并更新qmin,qmax,一旦arr[i..j]不满足条件,j向右扩张停止,此时,arr[i..j-1]、arr[i..j-2]... arr[i,i]是满足条件的,所以满足条件的子数组的数量为j-i个,令res+=j-i
- 完成2之后,i向右移动一个位置,同时更新qmin和qmax,并重复步骤2;
- 步骤3和步骤2,完成后,返回res即可
源码
public static int getNum(int[] arr,int num){
if(arr==null||arr.length==0||num<0){
return 0;
}
//双端队列,qmin存储从小到大,qmax存储从大到小
LinkedList<Integer> qmin=new LinkedList<Integer>();
LinkedList<Integer> qmax=new LinkedList<Integer>();
//arr[i..j]
int i=0;
int j=0;
//res是返回的子数组数量
int res=0;
while(i<arr.length){
//步骤2,右移j
while(j<arr.length){
//初次进入和之后的进入
if(qmin.isEmpty()||qmin.peekLast()!=j){
while(!qmin.isEmpty()&&arr[qmin.peekLast()]>=arr[j]){
qmin.pollLast();
}
qmin.addLast(j);
while(!qmax.isEmpty()&&arr[qmax.peekLast()]<=arr[j]){
qmax.pollLast();
}
qmax.addLast(j);
}
//不满足条件,跳出当前循环
if(arr[qmax.getFirst()]-arr[qmin.getFirst()]>num){
break;
}
j++;
}
//累加满足条件的子数组数量
res+=j-i;
//更新qmin和qmax当i右移时,如果是第一个元素,则需要弹出,否则没有影响
if(qmin.peekFirst()==i){
qmin.pollFirst();
}
if(qmax.peekFirst()==i){
qmax.pollFirst();
}
i++;
}
return res;
}
有问题,欢迎讨论