java学习笔记: 从滑动窗口最大值学优先队列&单调队列

问题

在这里插入图片描述
可以很容易想到一个超时的 O(kn)的解法。

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        int[] res = new int[n-k+1];
        for (int i = 0; i < n-k+1; i++) {
            int xx = -9999996;
            for (int j = 0; j < k; j++) {
                xx = Math.max(xx, nums[i+j]);
            }
            res[i] = xx;
        }
        return res;
    }
}

可以很明显的看出来,可以每次获取值时都重新比较了k次大小,但实际上相邻两次比较有许多的重复操作。

优先队列

队列Queue为一个先进先出的队列。
java中的队列都基于 Queue 实现, 因此使用时常常上溯至Queue。
队列常用的实现为 LinkedList。

Queue<Node> q = new LinkedList();
q.offer(root);
q.isEmpty()
q.poll()
q.peek()

常用api如上所示。
在滑动窗口问题中,可以形象的认为窗口的滑动过程是一个队列的出列入列的过程,在此过程中需要维护一个队列中所有元素的最大值。
那么有什么办法可以快速获取队列的最大值呢,答案呼之欲出了:优先队列: PriorityQueue。与队列一样,他需要支持两个最简单的API,获取最大元素(O(1)),插入元素(O(logn))。删除最大元素的复杂度则涉及到优先队列的底层实现。
优先队列的使用方法如下所示

import java.util.PriorityQueue;
import java.util.Queue;

Queue<Integer> q = new PriorityQueue<>(); // 在此可以传入一个比较器作为新的比较算法。
q.add(996);
q.isEmpty()
q.poll()
q.peek()

PriorityQueue与普通队列的区别:从队首获取元素时,总是获取优先级最高的元素。优先队列默认按元素比较的顺序排序,且java堆默认是小根堆。
重新定义排序算法如下所示(例如元素为数组或者元素)。 如果是p1-p2, 则从小到大排序,

PriorityQueue<int[]> pq = new PriorityQueue<int[]>(...);
/*  
... 填入new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        } 
*/

此题的单调队列解法如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        int[] res = new int[n-k+1];

        PriorityQueue<int[]> pq = new PriorityQueue<int[]>( new Comparator<int[]>() {
            public int compare(int[] pair1, int[] pair2) {
                return pair1[0] != pair2[0] ? pair2[0] - pair1[0] : pair2[1] - pair1[1];
            }
        } );
        for (int i = 0; i < k-1; i++) {
            pq.offer( new int[]{nums[i], i});
        }
        for (int i = k-1; i < n; i++) {
            pq.offer(new int[]{nums[i], i});
            int[] xx;
            do {
                xx = pq.poll();
            }while( !(xx[1]>=i-k+1));
            pq.offer(xx);
            res[i-k+1] = xx[0];
        }
        return res;
    }
}

优先队列(堆的应用):

  1. 堆排序(O(logn)的算法),但需要占用额外空间
  2. 任务调度,例如仿真系统,需要按照时间顺序依次调用,但元素的插入顺序往往与时间无关。(有空的话我会研究一下ns3的调度系统)
单调队列

单调栈的经典应用是寻找下一个最大元素。在这种情况下,单调栈可以为每个元素寻找右边第一个更大的元素,在遍历的过程中,如果找到了就出栈,找到一个更弱的就入栈。 在遍历过程中时,栈中的元素都是某种意义上的凸点:比我大的要比我早出现,没我早出现的一定没我大。

而在此题中,我们需要找的则是要更晚出现而且更大的值。我们想要获取一个没那么早出现的但却最大的一个值,这就需要从栈底取一个更大的值,此时单调队列的定义就呼之欲出了: 一个可以从栈底操作的单调栈:也就是额外增加了队尾操作的队列。
LinkedList就实现了这些API。

Deque<Node> q = new LinkedList();
q.offerFirst(root); 
q.offerLast(root); 

q.isEmpty()

q.pollFirst()
q.pollLast()

q.peekFirst()
q.peekLast()

使用此数据结构,得到此题的解 如下:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        int n = nums.length;
        Deque<int[]> pq = new LinkedList<int[]>();

        for (int i = 0; i < k; ++i) {
            while (!pq.isEmpty() && pq.peekLast()[0] <= nums[i]) {
                pq.pollLast();
            }
            pq.offerLast(new int[]{nums[i], i});
        }

        int[] ans = new int[n - k + 1];
        ans[0] = pq.peekFirst()[0];

        for (int i = k; i < n; ++i) {
            while (!pq.isEmpty() && pq.peekLast()[0] <= nums[i]) {
                pq.pollLast();
            }
            pq.offerLast(new int[]{nums[i], i});

            while (pq.peekFirst()[1] <= i - k) {
                pq.pollFirst();
            }

            ans[i - k + 1] = pq.peekFirst()[0];
        }
        return ans;
    }
}

双端队列的常见运用:
1.计算机组成原理中的LRU缓存:每当内存miss时添加元素到末尾并且最久未使用的元素(队首)出队,命中时则移到队尾,可以提高内存访问的命中率。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ko no 辉夜 da

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值