剑指offer面试题59(java版):队列的最大值

welcome to my blog

剑指offer面试题59(java版):队列的最大值

题目1描述: 滑动窗口的最大值

给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

第四次做; 核心: 1) 维护一个队列, 队列头部是当前滑动窗口中的最大值的索引; 队列里面存储的是索引信息, 并不是具体的值, 因为有了索引就能知道索引对应的值, 但是有了值却不能知道对应的索引, 所以说存储索引包含的信息量更大, 本题中就可以用索引检查队列头部索引对应的元素是否不在滑动窗口中; 2)更新res之前先检查队列头部索引是否过期
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        if(n==0 || k==0){
            return new int[]{};
        }
        LinkedList<Integer> queue = new LinkedList<>();
        int[] res = new int[n-k+1];
        int p=0;
        for(int i=0; i<n; i++){
            while(!queue.isEmpty() && nums[i]>nums[queue.peekLast()]){
                queue.pollLast();
            }
            queue.add(i);
            if(i -queue.peek()==k){
                queue.poll();
            }
            if(i+1>=k){
                res[p++] = nums[queue.peek()];
            }
        }
        return res;
    }
}
/*
维护一个队列: 队列的头部始终是当前窗口中的最大值的索引
*/
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        //假设窗口长度大于等于k并且k大于0; 没通过特殊案例:[], 0
        if(nums==null || nums.length==0 || k==0)
            return new int[]{};
        //
        LinkedList<Integer> queue = new LinkedList<>();
        int n = nums.length;
        int[] res = new int[n-k+1];
        int index=0;
        for(int i=0; i<n; i++){
            int cur = nums[i];
            while(!queue.isEmpty() && cur>nums[queue.getLast()]){
                queue.pollLast();
            }            
            //here, queue.isEmpty() || cur<=queue.getLast()
            queue.add(i);
            //更新res之前需要检查队列头部元素是否过期; 如果过期则删除
            if(i-queue.peek()>=k){
                queue.poll();
            }
            //如果窗口大小达到k则更新res;
            if(i+1>=k){
                res[index++] = nums[queue.peek()];
            }

        }
        return res;
    }
}

思路

  • 使用一个双端队列, 将数组中元素的索引依次入队, 入队过程中要始终保持队列首元素对应的值是当前滑窗中所有元素对应的值中最大的
  • 也就是如果当前待入队元素对应的值比队尾元素对应的值大时,要删除队尾元素, 再比较待入队元素对应的值和新队尾元素对应的值的大小关系, 比队尾元素对应的值大就删除队尾元素, 直到队首元素对应的值是队列所有元素对应的值中最大的
  • 如何知道队列首元素对应哪个滑窗? 当我们知道了滑窗的长度和滑窗中最后一个元素的索引时,就能确定滑窗第一个元素的索引, 比如当前待入队的元素索引是i,滑窗长度是size, 那么该滑窗中第一个元素的索引是i-size+1
  • 如果当前元素入队后,滑窗中的首元素索引小于i-size+1,说明该元素不属于当前滑窗了, 应该删掉
  • 总结一下就是: 所有的元素索引都要入队, 每一次入队(每一轮循环)可能执行下面四个操作
    • 根据大小关系决定是否删除队尾元素
    • 根据i-size+1的大小决定是否删除队首元素
    • 当前元素索引入队
    • 滑窗首元素的索引i-size+1一定大于等于0, 如果i-size+1>=0, 就将队列首元素添加到最终的结果集合中
第三次做; 队列中存储的是索引, 进行比较以及更新res的时候要用索引对应的元素, 不是索引, 集中注意力!
import java.util.ArrayList;
import java.util.LinkedList;

public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> res = new ArrayList<>();
        if(num==null || num.length==0 || size < 1)
            return res;
        LinkedList<Integer> ll = new LinkedList<>();
        for(int i=0; i<num.length; i++){
            //队列中存储索引, 方便判断队列中的元素是否超出滑窗大小
            //队列为空
            if(ll.isEmpty())
                ll.add(i);
            //队列不为空
            else{
                //队列不为空,且当前元素大于队尾索引对应的元素, 则去掉队尾, 直到队列为空或者当前元素小于队尾索引对应的元素
                while(!ll.isEmpty() && num[i]>num[ll.getLast()])
                    ll.removeLast();
                ll.add(i);
                //如果队尾索引减去队首索引加1大于滑窗长度,说明队首元素不在滑窗内了,去掉队首元素
                if(ll.getLast() - ll.getFirst() + 1 > size)
                    ll.removeFirst();
            }
            //i大于等于滑窗长度减1时滑窗才是满的
            if(i >= size-1)
                //队列中存的是索引! res中需要加入的是索引对应的元素!
                res.add(num[ll.peek()]);
        }
        return res;
    }
}
第二次做, 使用双端队列存储元素索引; 队列头部对应元素表示当前滑窗的最大值; 当前索引和队列首元素控制滑窗范围
  • 遍历数组中的每个元素
    • 如果队列为空,直接向队列添加当前元素
    • 如果队列不为空
      • 如果当前元素大于队列末尾对应的元素,则删除队列末尾元素,重复该过程,直到队列为空或者队列末尾对应的元素大于当前元素
        • 将当前元素索引加入队列末尾
    • 如果当前索引减去队列首元素索引大于等于size, 则说明队首对应元素不在当前滑窗的范围中,删除队首元素
    • 如果当前索引大于等于size-1说明滑窗已经框住了size个元素,将队列头部对应的元素添加到最终的ArrayList中
import java.util.LinkedList;
import java.util.ArrayList;

public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        LinkedList<Integer> deque = new LinkedList<>();
        ArrayList<Integer> res  = new ArrayList<>();
        //input check
        //size<=0也是特殊情况,要小心
        if(num==null || num.length==0 || size<=0)
            return res;
        
        for(int i=0; i<num.length; i++){
            //队列为空,直接加入元素即可
            if(deque.isEmpty())
                deque.add(i);
            //队列不为空,就存在比较关系了
            else{
                while(!deque.isEmpty() && num[i] > num[deque.peekLast()]){
                    deque.pollLast();
                }
                deque.add(i);
            }
            //判断队列首尾元素距离
            if(deque.peekLast() - deque.peek()>=size)
                deque.poll();
            //当前轮次滑动窗口的最大值
            if(i>=size-1)
                res.add(num[deque.peek()]);
        }
        return res;
    }
}
import java.util.ArrayList;
import java.util.ArrayDeque;
public class Solution {
    public ArrayList<Integer> maxInWindows(int [] num, int size)
    {
        ArrayList<Integer> al = new ArrayList<Integer>();
        //input check
        if(num.length<1 || num.length<size || size < 1)
            return al;
        //execute
        int lower_bound;
        ArrayDeque<Integer> ad = new ArrayDeque<Integer>();
        for(int i=0; i<num.length; i++){
            lower_bound = i - size + 1; //如果队列中的头元素小于lower_bound,就得把头元素删掉
            //队列为空则添加元素
            if(ad.isEmpty())
                ad.add(i);
            else{ // 队列不为空,则向队尾添加元素, 添加之前需要判断是否删除队列中的元素
                while(!ad.isEmpty() && num[i]>num[ad.getLast()]) // 访问队列中的元素时要确保队列不为空
                    ad.pollLast();
                ad.add(i);
            } 
            while(!ad.isEmpty() && ad.getFirst()<lower_bound)//如果队首元素小于lower_bound,说明队首元素不在当前滑窗的范围中,需要删除队首元素
                ad.pollFirst();
            if(lower_bound>=0)
                al.add(num[ad.getFirst()]);
        }
        return al;
    }
}

题目二

请定义一个队列并实现函数 max_value 得到队列里的最大值,要求函数max_value、push_back 和 pop_front 的时间复杂度都是O(1)。

若队列为空,pop_front 和 max_value 需要返回 -1

示例 1:

输入: 
["MaxQueue","push_back","push_back","max_value","pop_front","max_value"]
[[],[1],[2],[],[],[]]
输出: [null,null,null,2,1,2]
示例 2:

输入: 
["MaxQueue","pop_front","max_value"]
[[],[],[]]
输出: [null,-1,-1]
第一次做; 使用两个队列实现; 核心: 1)queue2头部保存的是queue1中的最大值, queue2其他部分是潜在的最大值, queue2中的元素排列规律:从队尾到队首是递增的; 2)弹出queue1中的元素时也要弹出queue2中的元素; 4)Queue是单端队列的接口; Deque是双端队列的接口
class MaxQueue {
    //存储入队元素
    private LinkedList<Integer> queue1;
    //存当前队列中的最大值
    private LinkedList<Integer> queue2;
    
    public MaxQueue() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }
    
    public int max_value() {
        return queue2.isEmpty()? -1 : queue2.peek();
    }
    
    public void push_back(int value) {
        queue1.add(value);
        //不等式不能取等, 为了让等于队尾元素的值入队
        while(!queue2.isEmpty() && value > queue2.getLast()){
            queue2.pollLast();
        }
        queue2.add(value);
        
    }
    
    public int pop_front() {
        if(queue1.isEmpty())
            return -1;
        int cur = queue1.pop();
        if(cur == queue2.peek())
            queue2.poll();
        return cur;
    }
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值