welcome to my blog
程序员代码面试指南第二版 10.最大值减去最小值小于或等于num的子数组数量
题目描述
题目描述
给定数组 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 和 num,其中 n 表示数组 arr 的长度
第二行输入n个整数,表示数组arr中的每个元素
输出描述:
输出给定数组中满足条件的子数组个数
示例1
输入
5 2
1 2 3 4 5
输出
12
第一次做; 核心: 寻找子数组的方式, 两个约束条件, 维护最大值最小值索引队列; 所有元素索引都会进队一次, 出队一次; 在内循环外面更新res
/*
核心: 正常来说要考察每个子数组是否满足条件, 如何找到所有的子数组? 常规的想法是按照子数组的长度进行寻找,
长为1的子数组, 长为2的子数组, ... ,长为x的子数组; 按长度遍历子数组的话, 需要每次都没从头遍历, 所以这种方法不行
需要找到只需要遍历一次的方式?
可以按照子数组的起点寻找子数组, 以arr[0]开头的子数组, 以arr[1]开头的子数组, 以arr[2]开头的子数组,...,以arr[x]开头的子数组
要习惯这种找子数组的方式, 结合下面两个约束,因为可以带来极大的便利!
对于子数组arr[i,..,j]来说,
1)如果arr[i,..,j]中的max - min <= num, 那么子数组arr[i,...,j-1], 子数组arr[i,...,j-2],...,
子数组arr[i,i+1], 子数组arr[i]都满足条件(更新右边界)
2)如果arr[i,...,j]中的max - min > num, 那么包含arr[i,...,j]的子数组都不满足条件, 此时只能以arr[i+1]开头寻找新的子数组(更新左边界)
*/
import java.util.Scanner;
import java.util.LinkedList;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String curr = sc.nextLine();
int n = Integer.parseInt(curr.split(" ")[0]);
int num = Integer.parseInt(curr.split(" ")[1]);
String[] str = sc.nextLine().split(" ");
int[] arr = new int[n];
for(int i=0; i<arr.length; i++)
arr[i] = Integer.parseInt(str[i]);
//
int res = 0;
//使用双端队列维护最小值的索引, 队首是当前子数组的最小值索引; 所有元素进队一次, 出队一次
LinkedList<Integer> qmin = new LinkedList<>();
//使用双端队列维护最大值的索引, 队首是当前子数组的最大值索引; 所有元素进队一次, 出队一次
LinkedList<Integer> qmax = new LinkedList<>();
//initialize
int left=0, right=0, max, min;
//每个right走到头或者当前子数组不满足题目不等式时, 还下一个left进行遍历
while(left < n){
while(right < n){
//先往队列中加入元素再进行下一步处理
//队列为空时
if(qmin.isEmpty()){
qmin.add(right);
qmax.add(right);
right++;
}
//队列不为空时
else{
//维护qmax
while(!qmax.isEmpty() && arr[right] > arr[qmax.peekLast()]){
qmax.pollLast();
}
//防止重复加入
if(qmax.isEmpty() || qmax.peekLast()!=right)
qmax.add(right);
//维护qmin
while(!qmin.isEmpty() && arr[right] < arr[qmin.peekLast()]){
qmin.pollLast();
}
if(qmin.isEmpty() || qmin.peekLast() != right)
qmin.add(right);
//进行下一步处理
//队首元素不在子数组范围中,就要删掉
if(qmax.peek()<left)
qmax.poll();
if(qmin.peek()<left)
qmin.poll();
max = arr[qmax.peek()];
min = arr[qmin.peek()];
if(max - min > num)
break;
right++;
}
}
//不在内循环中更新res, 因为如果在内循环中更新res的话, 没法考虑到right==n的情况
res += right - left;
left++;
}
System.out.println(res);
}
}
左神的高效简洁代码; 和我的逻辑是一样的, 但是进行了综合
import java.util.Scanner;
import java.util.LinkedList;
public class Main{
public static void main(String[] args){
Scanner sc = new Scanner(System.in);
String curr = sc.nextLine();
int n = Integer.parseInt(curr.split(" ")[0]);
int num = Integer.parseInt(curr.split(" ")[1]);
String[] str = sc.nextLine().split(" ");
int[] arr = new int[n];
for(int i=0; i<arr.length; i++)
arr[i] = Integer.parseInt(str[i]);
//
int res = 0;
//使用双端队列维护最小值的索引, 队首是当前子数组的最小值索引; 所有元素进队一次, 出队一次
LinkedList<Integer> qmin = new LinkedList<>();
//使用双端队列维护最大值的索引, 队首是当前子数组的最大值索引; 所有元素进队一次, 出队一次
LinkedList<Integer> qmax = new LinkedList<>();
//initialize
int i=0, j=0, max, min;
//每个right走到头或者当前子数组不满足题目不等式时, 还下一个left进行遍历
while(i<n){
while(j<n){
//维护队列
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;
//本轮外循环最后要进行i++操作, 如果现在的队列首元素==i, 说明队首元素不在下一轮循环的考虑范围(不在新的子数组范围中了)中了, 需要出队
if(qmin.peekFirst() == i)
qmin.pollFirst();
if(qmax.peekFirst() == i)
qmax.pollFirst();
i++;
}
System.out.println(res);
}
}