leetcode hard模式专杀之239. Sliding Window Maximum

原题:https://leetcode.com/problems/sliding-window-maximum/

 

这题的暴力解法很容易想到,没什么花头,就是遍历找最大值。

折中解法也不是很难想到,可以考虑用一个大顶堆来保存每次滑动一格后的情况(这里由于更新堆的时间复杂度为log(k),所以只能做到时间复杂度为O(nlog(k))。

 

可是题干偏偏有个follow up说存在O(n)的解法,这就比较难了,相当于说我需要用一个数据结构,维护一个集合,需要知道当下该集合中的最大值,同时做一次滑动窗口操作(删一个值,加一个值)之后,仍能知道最大值是谁,并且这个删&加的操作时间复杂度为O(1)时间复杂度,虽然这个需求理出来了,可是如何实现这么一个数据结构却犯了难。想了一阵子没想出来。后来看了 答案,是说用一个deque(双端队列)来实现以上要求的数据结构,而其本质思维是,不需要真的维护窗口大小k个元素来做最值判断,因为根据一些原则,是可以排除掉一部分元素的(剪枝的思维),例如窗口大小为3,右滑一步会发生新进入一个元素到窗口,同时推出一个元素出窗口,如果在滑动前,最大值为3,那么只要新滑入的值大于3,例如4,那么就可以确定未来这个3不可能成为最大值了(是指特定的这个3,而不是所有3),因为未来要么3被滑出窗口了,要么它后面还跟着个更大的4,所以怎么也轮不到它做老大,所以这种值就可以直接排除,而如果新进来的值比3小,例如是2,则在2被滑出前,还是有可能成为未来某窗口的最大值的,于是我们把这个2推入双端队列,总之最终的结果就是,双端队列最前端始终保持着当前窗口的最大值,后面的几个元素则保持着未来可能是最大值的候选项,每次滑动窗口时,按照如下原则更新这个双端队列,直到窗口滑到底,输出所有结果

更新双端队列的原则:

从队头开始往后扫描,只要扫到的元素小于新进入的元素,就把该元素踢出队

从堆尾开始往前扫描,只要扫到的元素小于新进元素,就把该元素踢出队

如队头元素已滑出窗口,则队头元素被踢出队

 

为了方便判断对头元素到底有没有被滑出窗口,双端队列可以记录数组元素的下标值,而不是记录数组的元素的真值,这样比较方便。

思路如上,代码如下:

package com.example.demo.leetcode;

import java.util.ArrayDeque;
import java.util.Deque;

public class SlidingWindowMax {

    /**
     *  sample input: nums = [1,3,-1,-3,5,3,6,7], and k = 3
     *  sample output: 3, 3, 5, 5, 6, 7
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        // ---边界条件考虑, nums为0或1, k>nums.length
        if(nums==null || nums.length<1){
            int[] non = new int[0];
            return non;
        }
        if(nums.length==1){
            return nums;
        }

        if(k>nums.length){
            k = nums.length;
        }

        int[] ret = new int[nums.length-k+1];

        Deque<Integer> dq = new ArrayDeque<>();

        for(int i=0;i<nums.length-k+1;i++){
            if(i==0){
                int initMaxIndex = initDequeue(nums, k, dq);
                ret[i] = nums[initMaxIndex];
            }else{
                // action: move from i-1 to i
                int maxIndex = slideWindow(nums, i, k, dq);
                ret[i] = nums[maxIndex];
            }
        }

        return ret;
    }

    public int initDequeue(int[] nums, int k, Deque<Integer> dq){
        for(int i=0;i<k;i++){
            magicIntoq(dq, i, nums);
        }
        return dq.getFirst();
    }

    private void magicIntoq(Deque<Integer> dq, Integer indexOfVal, int[] arr){
        if(dq.isEmpty()){
            dq.addFirst(indexOfVal);
        }else{
            //用新插入的值,循环前往后比头部,把比新值小的全出队
            while(!dq.isEmpty() && arr[dq.getFirst()]<=arr[indexOfVal]){
                dq.removeFirst();
            }
            //用新插入的值循环比尾部
            while(!dq.isEmpty() && arr[dq.getLast()]<=arr[indexOfVal]){
                dq.removeLast();
            }

            // 入队新值
            dq.addLast(indexOfVal);
        }
    }

    // assumption: i>0, 对应从窗口左侧从i-1滑动到i,
    public int slideWindow(int[] nums, int i, int k, Deque<Integer> dq){
        // 先考虑dq要不要推出一个元素
        if(dq.getFirst()==i-1){
            dq.removeFirst();
        }
        // 再尝试推入i+k-1下标
        magicIntoq(dq, i+k-1 , nums);

        return dq.getFirst();
    }

    public static void main(String[] args) {
//        int[] arr = {1,3,-1,-3,5,3,6,7};
//        int[] arr = {3};
//        int[] arr = {};
        int[] arr = {7,2,4};

        SlidingWindowMax demo = new SlidingWindowMax();
        int[] ret = demo.maxSlidingWindow(arr, 2);

        for(int i=0;i<ret.length;i++){
            System.out.println(ret[i]);
        }

    }
}

 

 

反思:

当时间复杂度怎么看都降不下去时,可以考虑从其他角度减枝,排除一些不可能的数据或选项,则可能降低时间复杂度!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值