import java.util.ArrayDeque;
import java.util.Comparator;
import java.util.PriorityQueue;
public class SlidingWindowMaximum {
//方法一 :暴力法 遍历每一个窗口 对每个窗口中的每个元素求最大值
public int[] maxSlidingWindow(int[] nums,int k){
//定义一个结果数组 总共有n-k+1个数组
int[] result = new int[nums.length-k+1];
//遍历数组,从0到n-k,作为滑动窗口的起始位置
for (int i = 0; i <=nums.length-k ; i++) {
//找窗口内的最大值 定义一个变量来保存
int max = nums[i];
//遍历窗口中的每一个元素,比较大小
for (int j = i+1; j <i+k ; j++) {
if(max<nums[j]){
max = nums[j];
}
}
result[i] = max;
}
return result;
}
//方法二:使用大顶堆
public int[] maxSlidingWindow2(int[] nums,int k){
//定义一个结果数组 总共有n-k+1个数组
int[] result = new int[nums.length-k+1];
//用优先队列实现一个大顶堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2-o1;
}
});
//准备工作 构建大顶堆 将第一个窗口元素(前k个)放入堆中
for (int i = 0; i <k ; i++) {
maxHeap.add(nums[i]);
}
//当前大顶堆的堆顶元素就是第一个窗口的最大值
result[0] = maxHeap.peek();
//遍历所有的窗口
for (int i = 1; i <=nums.length-k; i++) {
maxHeap.remove(nums[i-1]);//删除堆中
maxHeap.add(nums[i+k-1]);//添加当前窗口中最后一个元素进堆
result[i] = maxHeap.peek();
}
return result;
}
// 方法三:使用双向队列
public int[] maxSlidingWindow3(int[] nums, int k){
// 定义一个结果数组
int[] result = new int[nums.length - k + 1];
// 定义双向队列,保存元素的索引
ArrayDeque<Integer> deque = new ArrayDeque<>();
// 初始化双向队列,处理第一个窗口的数据
for (int i = 0; i < k; i++){
// 如果队尾元素小于当前元素,直接删除
while (!deque.isEmpty() && nums[i] > nums[deque.getLast()])
deque.removeLast();
deque.addLast(i);
}
result[0] = nums[deque.getFirst()]; // 第一个窗口的最大值
// 遍历数组所有元素,作为窗口的结束位置
for (int i = k; i < nums.length; i++){
// 判断如果上一个窗口删掉的就是窗口最大值,那么需要将队列中的最大值删掉
if (!deque.isEmpty() && deque.getFirst() == i - k)
deque.removeFirst();
// 判断新增元素是否可以删除队尾元素
// 如果队尾元素小于当前元素,直接删除
while (!deque.isEmpty() && nums[i] > nums[deque.getLast()])
deque.removeLast();
deque.addLast(i);
// 保存结果
result[i - k + 1] = nums[deque.getFirst()];
}
return result;
}
//方法四:左右扫描
public int[] maxSlidingWindow4(int[] nums, int k){
int n = nums.length;
// 定义一个结果数组
int[] result = new int[n - k + 1];
//定义存放块内最大值的left和right数组
int[] left = new int[n];
int[] right = new int[n];
//遍历数组左右扫描
for (int i = 0; i <n ; i++) {
//1.从左到右扫描
if(i%k==0){
//如果能整除k,就是块的起始位置
left[i] = nums[i];
}else {
//如果不是起始位置,就直接跟left[i-1]取最大值即可
left[i] = Math.max(left[i-1],nums[i]);
}
//2.从右到左扫描
int j = n-1-i;//j就是倒数的i
if(j%k==k-1||j==n-1){
//就是块的起始位置
right[j] = nums[j];
}else {
//如果不是起始位置,就直接跟left[i-1]取最大值即可
right[j] = Math.max(right[j+1],nums[j]);
}
}
//对每个窗口计算最大值
for (int i = 0; i < n-k+1; i++) {
result[i] = Math.max(right[i],left[i+k-1]);
}
return result;
}
public static void main(String[] args) {
int[] input = {1,3,-1,-3,5,3,6,7};
int k = 3;
SlidingWindowMaximum slidingWindowMaximum = new SlidingWindowMaximum();
int[] output = slidingWindowMaximum.maxSlidingWindow4(input, k);
for (int max:output) {
System.out.println(max);
}
}
}