代码随想录算法训练营第三期day13-栈与队列03

目录

1.T239:滑动窗口最大值

思路

代码实现

法1、自定义单调队列

法2、用元素索引代替元素

2.T347:前K个高频元素

代码实现

大顶堆

小顶堆


1.T239:滑动窗口最大值

T:给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值 。

提示:

  • 1 <= nums.length <= 105

  • -104 <= nums[i] <= 104

  • 1 <= k <= nums.length

进阶:

你能在线性时间复杂度内解决此题吗?

S:

思路

个人认为本题应该是代码随想录刷题主线(不算“其他题目”)至今最难的一题

        如果考虑每次都怎么在滑动窗口中求局部最大值,那样时间复杂度绝对超出线性,目测O(n*k)起步

首先,可以明确的是:滑动窗口每次移动,都必定会有1个数进窗,有1个数出窗;

        从这一角度出发,我们能不能使用以栈或队列数据结构实现的容器,使得每次弹出的数都是局部最大值?

        很明显,没有现成的,但是可以自己实现!

代码实现

法1、自定义单调队列

class MyQue {
    private Deque queue = new ArrayDeque<>();

    // 看一下当前要弹出的数值,是否等于队列出口元素的数值,相等才弹出
    public void poll(int value) {
        if (!queue.isEmpty() && queue.getFirst() == value) {
            queue.poll();
        }
    }

    // 如果push的数值大于入口元素的数值,那么就将队列后端的数值一个一个地弹出,直到push的数值小于等于队列入口元素的数值
    // 这样就保证了队列里的数值是(从队头到队尾)单调非递增的
    public void add(int value) {
        while (!queue.isEmpty() && value > queue.getLast()) {
            queue.removeLast();
            // queue.poll();
        }
        queue.add(value);//addLast
    }
        
    // 查询当前队列里的最大值 直接返回队列前端也就是front就行
    public int peek() {
        return queue.getFirst();//等效于peek
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums == null || nums.length <= 1) return nums;
        MyQue myque = new MyQue();
        int[] res = new int[nums.length - k + 1];//数组的长度
        // 先将前k的元素放进队列
        for (int i = 0; i < k; ++i) {
            myque.add(nums[i]);
        }
        int num = 0;
        res[num++] = myque.peek();//漏了
        for (int i = k; i < nums.length; ++i) {
            myque.poll(nums[i - k]);// 滑动窗口移除最前面元素(当然不是一定会移除)
            myque.add(nums[i]);
            res[num++] = myque.peek();
        }
        return res;   
    }
}

法2、用元素索引代替元素

    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        for(int i = 0; i < n; i++) {
            // 根据题意,i为nums下标,是要在窗口[i - k + 1, i]中选最大值,只要保证两点
            // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            while(!deque.isEmpty() && deque.peek() < i - k + 1) {//难点!~法1中比较队头元素是否等于要出窗的value
            // while(!deque.isEmpty() && nums[deque.peek()] == nums[i - k + 1]) {
                deque.poll();
            }
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
                deque.pollLast();
            }

            deque.offer(i);

            // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行
            if(i >= k - 1){
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }

其实不管是法1还是法2,关键点就俩:

  1. 移除:滑动窗口移动的时候,检查一下被移出去的元素(或其索引)是否还在求最大值的考虑范围内(被移出去就意味着,已经没有可能是局部最大值),如果还在(在也只可能在队头),就移出去(就算还在,之前也肯定已经作为最大值被用过了),否则什么也不用做;
  2. 加入:元素(或其索引)进入队列的时候,将前面所有不如自己大的全部踢出去,从而实现队列中的元素(或队列中存放索引对应的元素)是从队头到队尾单调非递增的。

2.T347:前K个高频元素

T:给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

S:介于时间复杂度的要求和暗示,应该是要用二叉树或类似结构。

堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。

本题就非常适合使用优先级队列来对部分频率进行排序。

代码实现

大顶堆

在Java中用大顶堆更加简单

    public int[] topKFrequent(int[] nums, int k) {
        if (nums == null || nums.length <= 1 || k < 1) return nums;
        
        Map map = new HashMap<>();
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);//这个方法好!
        }
        //用优先队列:加入队列会自动按规则排序
        PriorityQueue queue = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
        for (Map.Entry entry : map.entrySet()) {
            queue.add(new int[]{entry.getKey(), entry.getValue()});
        }
        int[] res = new int[k];
        for (int i = 0; i < k; ++i) {
            // res[i] = queue.poll();
            res[i] = queue.poll()[0];
        }
        return res;
    }

小顶堆

        既然题目中说“返回其中出现频率前 k 高的元素,顺序任意”,那么用小顶堆也完全没问题,只要队列中元素达到k个以后,每次新加元素的时候,看看要不要把小的踢出去就行~

        PriorityQueue queue = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
        for (Map.Entry entry : map.entrySet()) {
            if (queue.size() < k) {
                queue.add(new int[]{entry.getKey(), entry.getValue()});
            } else if (entry.getValue() > queue.peek()[1]) {
                queue.poll();
                queue.add(new int[]{entry.getKey(), entry.getValue()});
            }
        }

你可能感兴趣的:(算法,数据结构,java,leetcode)