手把手带你手写一个单调队列

带你手写一个单调队列

一、概述

​ 本文将带你彻底搞懂单调队列的原理,并用代码进行实现。有一到经典的用单调队列解决的LeetCode题——滑动窗口最大值

二、原理

​ 首先先看看这个单调队列用于解决的问题:用于确定当前的最值,当然,也可以确定滑动窗口的最值。

我在这里举个例子你们就会明白:

​ 目前有这样一个帮会,实例最强的就可以当老大,实力我们用数值来表示,帮会人数最多为3人。下面是进入帮会的顺序:1,3,-1,-3,5,3,6,7,因为最多只能有3个人,所以当第四个进来的时候,之前最先进来的那一个就要出去。

​ 基于上面的规则,现在我们演示一下流程

1、先进来1

此时队列中的数据:[1]	//现在1是老大

2、后面进来了3

此时队列中的数据:[3]	//因为1发现后面来的3比自己还要强,因此肯定是没有可能再当老大了,于是就退出了

3、接着进来了-1

此时队列中的数据:[3,-1]	// -1进来后发现如果3退休了,自己就有可能当老大,于是留下了

4、之后-3也是和上面一样想的

此时队列中的数据:[3,-1,-3]	// -3进来后发现如果3退休了,自己就有可能当老大,于是留下了

5、此时5来了,队列长度以及大于3了,于是最早进来的3就出队列了

此时队列中的数据:[5]	// 5进来后,其他人就会想,后来的人都这么厉害了,留下来肯定不可能当老大了,于是-1和-3退出了队列

之后的步骤相信大家肯定可以自己想到,总而言之,每次来一个新的成员的时候,值比其小的成员都会退出队列,因为再往后肯定是不可能会成为最大的了!理解了 这个后,下面我们通过代码来实现一个单调递增队列

三、代码实现

这里以LeetCode的第239题——滑动窗口最大值为例。这道题中单调队列其实核心的就两个操作:①检查第一个是否该退出。②判断哪些节点要退出队列。代码的解释全部写在了注释中

public int[] maxSlidingWindow(int[] nums, int k) {
    
    
		// 特殊情况判断
        if (nums.length == 0) {
    
    
            return new int[]{
    
    };
        }
        if (k == 1) {
    
    
            return nums;
        }
        // 返回的结果
        int[] res = new int[nums.length - k + 1];
        MyQueue queue = new MyQueue(k);
        // 先放k个元素进去
        for (int i = 0; i < k; i++) {
    
    
            queue.put(nums[i], i);
        }
        // 后面的元素一个一个进来
        for (int i = k; i < nums.length; i++) {
    
    
            // 取出当前的最大值
            res[i - k] = queue.getMaxVal();
            // 放入新的元素
            queue.put(nums[i], i);
        }
        res[res.length - 1] = queue.getMaxVal();
        return res;
    }

	/**
	* 自定义的单调递增队列
	*/
    class MyQueue {
    
    
        /**
         * Node节点
         */
        private class Node {
    
    
        	// 数值
            int val;
            // 下标(用于判断是否该出队列)
            int index;
            // 指针
            Node pre, next;

            public Node(int val, int index) {
    
    
                this.val = val;
                this.index = index;
            }
        }

        /**
         * 头指针
         */
        private Node head;

        /**
         * 尾指针
         */
        private Node tail;

        /**
         * Node数量
         */
        private int capacity;

        /**
         * 队列的最大容量 
         *
         * @param capacity
         */
        public MyQueue(int capacity) {
    
    
            this.capacity = capacity;
        }

        public void put(int val, int index) {
    
    
            Node node = new Node(val, index);
            // 检查第一个Node是否该退出了
            checkHeadNode(index);
            // 检查是否右节点要出队
            refreshQueue(val);
            // 添加新节点
            addNodeToLast(node);
        }

        private void checkHeadNode(int maxIndex) {
    
    
            // 有才会检查
            if (head != null) {
    
    
                if (head.index < maxIndex - capacity + 1) {
    
    
                    head = head.next;
                    if (head != null) {
    
    
                        head.pre.next = null;
                        head.pre = null;
                    }
                }
            }
        }

        private void refreshQueue(int newVal) {
    
    
            // 可能节点会退出
            while (tail != null && tail.val < newVal) {
    
    
                tail = tail.pre;
                // tail现在是倒数第二个Node
                if (tail != null) {
    
    
                    tail.next.pre = null;
                    tail.next = null;
                } else {
    
    
                    // 元素删完了
                    head = null;
                }
            }
        }

        private void addNodeToLast(Node node) {
    
    
            // head = tail = null
            if (head == null && tail == null) {
    
    
                // 第一个Node
                head = node;
                tail = node;
            } else {
    
    
                // 拼接在后面
                tail.next = node;
                node.pre = tail;
                tail = node;
            }
        }

		/**
		* 获取当前队列的最大值
		*/
        public int getMaxVal() {
    
    
            if (head != null) {
    
    
                return head.val;
            } else {
    
    
                throw new RuntimeException("队列中没有元素");
            }
        }

		/**
		* 调式使用的方法,打印当前队列
		*/
        private void showQueue() {
    
    
            Node temp = head;
            System.out.println("===========================");
            while (temp != null) {
    
    
                System.out.printf(temp.val + " ");
                temp = temp.next;
            }
            System.out.println("===========================");
        }
    }

猜你喜欢

转载自blog.csdn.net/weixin_44829930/article/details/121863668