算法训练营day12|栈与队列|239.滑动窗口最大值,347.前K个高频元素

1.知识点

滑动窗口的最大值:

通过双端队列维护一个单调递减队列,队头是最大元素

前K个高频元素:通过最小优先队列实现

2.刷题

239.滑动窗口最大值

LeetCode链接 239. 滑动窗口最大值 - 力扣(LeetCode)

题目描述

方法1:滑动窗口+单调递减队列

package daimasuixiangshuati.day12_zhanyuduilie;

import java.util.Deque;
import java.util.LinkedList;

/**
 * @Author LeiGe
 * @Date 2022/10/2
 * @Description todo
 */
public class HuaDongChuangKouZuiDaZhi239_2 {

    /**
     * 方法1:滑动窗口+通过双端队列维护一个单调递减队列
     * 0.单调递减队列,队列头为最大值
     * 1.先形成第1个窗口
     * 2.滑动窗口
     * 2.1滑动窗口移除左侧元素 移除时,判断该元素是否是队列中的最大值,如果是,队头元素出队列
     * 2.2滑动窗口加入右边的元素 加入时,判断队列中是否有比该元素小的值,如果有,删除这些比当前值小的元素
     *
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) {
            return nums;
        }

        //有多少个最大值
        int len = nums.length - k + 1;
        //存放结果元素
        int[] res = new int[len];
        //记录最大值
        int num = 0;
        //自定义队列
        MyQueue myQueue = new MyQueue();
        //先将前k个元素放入队列中
        for (int i = 0; i < k; i++) {
            myQueue.add(nums[i]);
        }
        //当前窗口内最大值
        res[num++] = myQueue.peek();
        //开始滑动窗口
        for (int i = k; i < nums.length; i++) {
            // 滑动窗口移除左侧元素
            // 移除时,判断该元素是否是队列中的最大值,如果是,队头元素出队列
            myQueue.poll(nums[i - k]);

            // 滑动窗口加入右边的元素
            // 加入时,判断队列中是否有比该元素小的值,如果有,删除这些比当前值小的元素
            myQueue.add(nums[i]);
            //记录对应的最大值
            res[num++] = myQueue.peek();
        }
        return res;
    }

    /**
     * 自定义数据结构,单调递减队列:队头永远是最大值
     */
    private class MyQueue {
        Deque<Integer> deque = new LinkedList<>();

        //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
        //同时判断队列当前是否为空
        public void poll(int val) {
            if (!deque.isEmpty() && val == deque.peek()) {
                deque.poll();
            }
        }

        //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
        //保证队列元素单调递减
        //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
        public void add(int val) {
            while (!deque.isEmpty() && val > deque.getLast()) {
                deque.removeLast();
            }
            deque.addLast(val);
        }

        //队列队顶元素始终为最大值
        int peek() {
            return deque.peek();
        }
    }
}

时间复杂度:O(N)

空间复杂度:O(N)

方法2:滑动窗口+单调递减队列

package daimasuixiangshuati.day12_zhanyuduilie;

import java.util.LinkedList;

/**
 * @Author LeiGe
 * @Date 2023/10/29
 * @Description todo
 */
public class HuaDongChuangKouZuiDaZhi239_3 {

    /**
     * 方法2:方法2:滑动窗口+通过双端队列维护一个单调递减队列
     *
     * @param nums
     * @param k
     * @return
     */
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length < 2) {
            return nums;
        }

        // 双端队列
        LinkedList<Integer> dequeue = new LinkedList<>();

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

        for (int i = 0; i < nums.length; i++) {

            // 保证队列从大道小,如果前面的数小则需要弹出
            while (!dequeue.isEmpty() && nums[dequeue.peekLast()] <= nums[i]) {
                dequeue.pollLast();
            }

            // 添加当前值对应的数组下标
            dequeue.addLast(i);

            // 判断当前队列中队首元素是否有效
            if (dequeue.peek() <= i - k) {
                dequeue.pop();
            }

            // 当窗口长度为k时,保存当前窗口中最大值
            if (i + 1 >= k) {
                result[i + 1 - k] = nums[dequeue.peek()];
            }
        }

        return result;
    }
}

时间复杂度:O(N)

空间复杂度:O(N)

347.前K个高频元素

LeetCode链接 347. 前 K 个高频元素 - 力扣(LeetCode)

题目描述

方法1:最小优先队列(基于堆实现)

package daimasuixiangshuati.day12_zhanyuduilie;

import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;
import java.util.Set;

/**
 * @Author LeiGe
 * @Date 2022/10/2
 * @Description todo
 */
public class QianKGeGaoPinYuanSu347_2 {

    /**
     * 方法1:最小优先队列
     * 1.通过map统计出数组中每个元素出现的频率
     * 2.构建1个最小优先队列,将map中的所有元素加入到最小优先队列中
     * 2.1如果加入的过程中,队列大小大于K,则将顶端元素弹出(队列中维持k个元素,顶端最小)
     * 3.队列中的k个元素就是最大的k个元素
     *
     * @param nums
     * @param k
     * @return
     */
    public int[] topKFrequent(int[] nums, int k) {
        int[] result = new int[k];

        // 统计每个元素出现的频率
        HashMap<Integer, Integer> map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }

        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        // 根据map的value值正序排,相当于一个小顶堆
        PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o1.getValue() - o2.getValue());
        //将所有频率加入到小顶堆中,如果如果堆大小大于k,弹出(相当于弹出最小值)
        for (Map.Entry<Integer, Integer> entry : entries) {
            queue.offer(entry);
            if (queue.size() > k) {
                queue.poll();
            }
        }
        //倒序数组输出结果
        for (int i = k - 1; i >= 0; i--) {
            result[i] = queue.poll().getKey();
        }
        return result;
    }
}

时间复杂度:O(N*logN)

空间复杂度:O(N)

3.小结

栈相关的题目:

栈在系统中的应用

括号匹配问题

字符串相邻去重

逆波兰表达式

队列相关的题目:

滑动窗口最大值

前K个高频元素

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值