要求:
给定数组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)的解法。
思考:
普通的解法找到arr所有的子数组O(N²)个,然后对每个子数组求max和min,过程时间复杂度为N²·N,此方法行不通。现在的方法如下:生成两个双端队列qmax和qmin,当子数组为arr[i..j]时,qmax维护了窗口子数组arr[i..j]的最大值更新的结构,qmin维护了窗口子数组arr[i..j]的最小值更新的结构。当子数组arr[i..j]向右扩一个位置变成arr[i..j+1]时,qmax和qmin结构可以在O(1)的时间内更新,并且可以在相同的时间内得到arr[i..j+1]的最大值和最小值。同时可以得出两个结论:
- 如果子数组arr[i..j]满足条件,那么arr[k..l](i<=k<=l<=j)都满足条件。
- 如果子数组arr[i..j]不满足条件,那么arr[k..l](k<=i<=j<=l)都不满足条件。
1.生成qmax和qmin,同时生成两个整型变量i和j,表示子数组的范围,即arr[i..j],生成整型变量res,表示所有满足天骄的子数组数量。
2.令j不断向右移动(j++),表示arr[i..j]一直向右扩大,不断更新qmax和qmin结构。一旦出现arr[i..j]不满足条件的情况,j向右扩的过程停止,此时arr[i..j-1]、arr[i..j-2]、arr[i..j-3]、……、arr[i..i]都是满足条件的。也就是说满足条件的个数为j - i,即令res = j - i 。
3.完成步骤2,再令i向右移动一个位置,并对qmax和qmin进行更新,此时是arr[i+1..j]窗口的最大值和最小值的更新结构。然后反复重复步骤2。
4.根据步骤2和3,依次求出以arr[0]、arr[1]……、arr[N]作为第一个元素的子数组中满足条件的数量分别有多少个,累积起来的数量就是最终的结果。
整个过程由于小标值最多进qmax和qmin一次和出一次,所以过程的时间复杂度为O(N)。
实现代码:
package algorithm_8;
import java.util.LinkedList;
public class algorithm_8 {
public static int getNum(int[] arr , int num){
if (arr == null || arr.length == 0){
return 0 ;
}
LinkedList<Integer> qmin = new LinkedList<Integer>();
LinkedList<Integer> qmax = new LinkedList<Integer>();
int i = 0;
int j = 0;
int res = 0;
while(i < arr.length){
while (j < arr.length){
while(!qmin.isEmpty() && arr[qmax.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++;
}
if(qmin.peekFirst() == i){
qmin.pollFirst();
}
if(qmax.peekFirst() == i){
qmax.pollFirst();
}
res += j - i;
i++;
}
return res;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
int[] arr_test = {1,2,3,4,5,6,7,8,9};
int num_test = 4;
int res_test;
res_test = getNum(arr_test,num_test);
System.out.printf("res = %d",res_test);
}
}
实现结果:
res = 41