和不超过k的子数组的数量
和不超过k子数组的数量
给定一个整型数组arr,和一个整数k
某个arr中的子数组sub,如果想达标,必须满足:
sub中最大值 – sub中最小值 <= num,
返回arr中达标子数组的数量
解题思路
这题如果用暴力解法求解的话,首先要循环遍历每个位置.,每个位置上我们要再往后在右边的位置上去求出最大值和最小值,去判断是否满足需求,就会是一个三次方的时间复杂度.
代码演示
/**
* 暴力解法
* @param arr
* @param k
* @return
*/
public static int right(int[] arr, int k) {
if (arr == null || arr.length == 0 || k < 0) {
return 0;
}
int N = arr.length;
int count = 0;
//L 从0 开始
for (int L = 0; L < N; L++) {
//到R 之间的一个范围
for (int R = L; R < N; R++) {
int max = arr[L];
int min = arr[L];
//在这个范围内判断是否满足需求
for (int i = L + 1; i <= R; i++) {
max = Math.max(max, arr[i]);
min = Math.min(min, arr[i]);
}
if (max - min <= k) {
count++;
}
}
}
return count;
}
上面解法 时间会很慢,我们可以把他优化成O(n) 的时间复杂度.我们用窗口最大值和最小值的更新结构去更新维护一个窗口内的最大值和最小值,那么我们每次移动一个位置,就可以随时判断是否满足需求,窗口一直向右移动不回退,所以只遍历一次,所以是O(n)的复杂度,下面看代码.
代码演示
/**
* 窗口最大值和最小值的更新结构来判断子数组是否满足需求
* @param arr
* @param k
* @return
*/
public static int num(int[] arr, int k) {
if (arr == null || arr.length == 0){
return 0;
}
int N = arr.length;
int count = 0;
//窗口最大值结构,里面维护的是数组下标值
LinkedList<Integer> maxWindow = new LinkedList<>();
//窗口最小直结构.维护数组下标值
LinkedList<Integer> minWindow = new LinkedList<>();
int R = 0;
//下面L 和 R 虽然是两个循环,但L 和 R 都是不回退的,还是一次遍历,所以复杂度还是O(n)
for (int L = 0;L < N;L++){
while (R < N){
//维持最大窗口结构,弹出前面所有比arr[R] 小的数
while (!maxWindow.isEmpty() && arr[maxWindow.peekLast()] <= arr[R]){
maxWindow.pollLast();
}
//把R加进来,
maxWindow.addLast(R);
//维持最小窗口结构,弹出前面所有比arr[R]大的数,
while (!minWindow.isEmpty() && arr[minWindow.peekLast()] >= arr[R]){
minWindow.pollLast();
}
//把R 加进来
minWindow.addLast(R);
//判断最大值减最小值是否满足需求,R 来到第一个不满足的位置停止
if (arr[maxWindow.peekFirst()] - arr[minWindow.peekFirst()] > k){
break;
}else{
R++;
}
}
//在一个范围内如果满足,那么认为这个范围内的所有子数组都满足需求
//比如数组下标1 到 5 位置满足,那么1 到2 ,1到3,1 到4都是满足的,
//所以数量是R - L
count += (R - L);
//检查窗口位置,头位置过期就弹出
if(maxWindow.peekFirst() == L){
maxWindow.pollFirst();
}
if (minWindow.peekFirst() == L){
minWindow.pollFirst();
}
}
return count;
}
如果窗口最大值最小值的更新结构不懂,可以查看
滑动窗口最大值的更新结构