1. 生成窗口最大值数组
题目:有一个整型数组arr和一个大小为w的窗口从数组的最左边滑到最右边,窗口每次向右边滑一个位置。
例如,数组为【4,3,5,4,3,3,6,7】,窗口大小为3时:
窗口数组 最大值
[4 3 5] 4 3 3 6 7 max: 5
4 [3 5 4] 3 3 6 7 max: 5
4 3 [5 4 3] 3 6 7 max: 5
4 3 5 [4 3 3] 6 7 max: 4
4 3 5 4 [3 3 6] 7 max: 6
4 3 5 4 3 [3 6 7] max: 7
思路:LinkedList
为一个标准的双端队列,可以将其作为窗口。
1.1 窗口结构
窗口可以由双端队列实现。从队尾进,从队首出。有两个指针分别为head,tail,只增不减,代表窗口只往前走,不往后退。
-
添加进窗口中的数据格式
往窗口中添加的数据是
[index, value]
,其中index表示该值是添加到该窗口的第几个元素。如果能通过index找到value的话,只往窗口中添加index即可。 -
往窗口中添加数据
先判断窗口中最后一个元素(即双端队列队尾元素)的值
last
是否不大于要加入的值cur
,如果不大于,弹出last
值。直到该窗口中最后的元素大于cur
值。(也就是说即使队尾元素与cur相等,也要被踢出去。) -
删除窗口中的元素
如果队首元素的
index
值过期,就需要将其从队首弹出。所谓”过期“,就是窗口往前滑动的过程中,新的元素添加进来,旧的元素出去。通过当前将要添加到窗口的
index
值与队首第一个元素的index
比较,如果相差大于窗口的容量,则队首元素过期。
1.2 代码
import java.util.LinkedList;
class Solution_MaxWindow{
//w为窗口大小
public static int[] getMaxWindow(int[] arr, int w){
if(arr == null || w < 1){
return null;
}
int len = arr.length;
//用来存放每一个窗口中的最大值
int[] maxs = new int[len - w + 1];
//使用双端队列结构实现滑动窗口
LinkedList<Integer> list = new LinkedList<Integer>();
int index = 0;
//遍历数组,将其存入窗口
for(int i = 0; i < len; i++){
//保证值最大的元素排在最首。
while(list.peekLast() != null && arr[list.peekLast()] <= arr[i]){
list.pollLast();
}
list.addLast(i);
//过期条件如下
if(i - list.peekFirst() == w){
list.pollFirst();
}
//从w-1开始,往maxs数组里添加窗口的最大值。
if(i >= w - 1){
maxs[index++] = arr[list.peekFirst()];
}
}
return maxs;
}
public static void printArray(int[] arr){
for(int value : arr){
System.out.print(value + " ");
}
System.out.println();
}
public static void main(String[] args){
int[] arr = new int[]{5,4,3,0,4,3,1,3,2,5};
int[] maxs = getMaxWindow(arr, 3);
printArray(maxs);
}
}
2. 最大值减去最小值小于等于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]中的最小值。
背景:
- 如果一个数组arr满足 最大值减去最小值小于等于num,那么这个数组的任意一个子数组也满足。
- 如果一个数组arr不满足 最大值减去最小值小于等于num,那么再加上任意一个元素,依旧不满足。
思路:
- 使用两个双端队列,一个用来存当前窗口的最大值,两一个用来存当前窗口的最小值。只不过窗口的容量是动态的。窗口的左右两个端点为
left
和right
。 - 遍历数组,每遍历一个数,分别得到当前窗口的最大值和最小值。
- 如果遇到一个数,使当前子数组不满足要求,此时包含当前窗口第一个元素的子数组都满足。则
res += right - left
。 - 然后窗口左端点右移一位,即
left++
,然后接着遍历。 - 直到
right
和left
都移动到数组的最后一位。
例:有数组[3,7,4,6,8]
,num=2
,窗口滑动过程如下(为了便于观看,图中两个双端队列里的值为元素值,实际双端队列里存的是下标。)
- 第一步:窗口初始大小为1,最大值和最小值都为1,满足条件,
- 第二步:right加1,最大值跟改为7,最小值为3,不满足条件,此时窗口中子数组
[3]
满足条件,结果加1,left加1。 - 第三步:right加1,最大值仍旧为7,随着窗口的滑动,最小值变为4,不满足条件。此时窗口中子数组
[7]
满足条件,结果加1,left加1。 - 第四步:right加1,最大值为6,最小值变为4,满足条件。left不变。
- 第五步:right加1,最大值为8,最小值变为4,满足条件。left不变。
- 第六步:right已经到数组最右,此时窗口中子数组
[4]
[4,6]
[4,6,8]
满足条件,结果加3。left加1。 - 第七步:此时窗口中子数组
[6]
满足条件,结果加1。left加1。 - 第八步:此时窗口中子数组
[8]
满足条件,结果加1。left已到数组最右,结束。
代码:
import java.util.LinkedList;
class Solution_GetSum2{
public static int getSum(int[] arr, int num){
if(arr == null || arr.length == 0 || num < 0){
return 0;
}
//最后结果
int result = 0;
//实现两个窗口,分别记录窗口中的最大值和最小值。
LinkedList<Integer> maxList = new LinkedList<Integer>();
LinkedList<Integer> minList = new LinkedList<Integer>();
int left = 0;
int right = 0;
while(left < arr.length){
while(right < arr.length){
//分别求出当前窗口中的最大值和最小值。
while(!maxList.isEmpty() && arr[maxList.peekLast()] <= arr[right]){
maxList.pollLast();
}
while(!minList.isEmpty() && arr[minList.peekLast()] >= arr[right]){
minList.pollLast();
}
maxList.addLast(right);
minList.addLast(right);
if(arr[maxList.peekFirst()] - arr[minList.peekFirst()] > num){
break;
}
right++;
}
if(maxList.peekFirst() == left){
maxList.pollFirst();
}
if(minList.peekFirst() == left){
minList.pollFirst();
}
result += right - left;
left++;
}
return result;
}
public static void main(String[] args){
int[] arr = new int[]{3,7,4,6,8};
System.out.println(getSum(arr, 2));
}
}