代码随想录算法训练营day13 | 239. 滑动窗口最大值 347.前 K 个高频元素

109 阅读4分钟

239. 滑动窗口最大值

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

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
    返回 滑动窗口中的最大值

239. 滑动窗口最大值 - 力扣(Leetcode)

思路

在遍历数组时,先遍历到的元素先进入滑动窗口,同样也先离开窗口,满足队列的先进先出的性质,因此这题可以使用队列来解决。
滑动窗口的长度是固定的,所求的结果是遍历过程中滑动窗口中的最大值,我们可以将队列中的元素按非递增数列进行排列,那么,队列的队首元素就是当前滑动窗口的最大值

  • 为保持队列中元素的非递增性,在遍历过程中,入队和出队操作需要进行调整,
  • 入队操作:如果 队尾元素 小于 当前元素,则将队尾元素入队,直到 队空 或者 队尾元素 大于等于 当前元素,然后再将当前元素入队。
    • 如果队尾元素小于当前元素,当前元素从队尾插入后,不会破坏队列的非递增性;
    • 如果队尾元素大于当前元素,当前元素从队尾插入后,队列元素会先递减再递增,因此需要将队列中大于当前元素的元素都出队。
    • 为什么出队队列中大于当前元素的元素不会影响滑动窗口的最大值呢
      1. 队列中大于当前元素的元素必然于当前元素移出滑动窗口
      2. 当它们还在滑动窗口内时,这些元素都小于当前元素,因此最大值不可能是这些元素
      3. 因此,这些大于当前元素的元素不在队列中不仅不影响结果,而且还可以让队列中的元素保持非递增性。
  • 出队操作:当队首元素与当前要移出滑动窗口的元素相同时,将当前元素出队,否则,不做任何操作。
    • 队首元素与当前要移出滑动窗口的元素(假设为 m)不同时,意味着 m 在之前的操作中已经出队了,即当 m 还在队列中时,入队了一个大于该元素值的元素,在其入队的过程中,将 m 出队。
    • 如果此时滑动窗口中有相同的元素(比如为 n),n 在队列中的个数与滑动窗口中的个数保持一致,这是为了避免n出队时,如果队列中只保存了一个 n,滑动窗口的其他k也会随之在队列中消失;
    • 由于n可以在出队操作的时候出队,这意味着n已是当前滑动窗口的最大值,一旦n出队,而入队了一个小于n的元素,那么下一步的滑动窗口的最大值还是n,而由于队列中只保存了一个 n且已出队,下一步的结果必然小于n答案不正确
    • 因此,我们强调的是队列中保持非递增性,而不是递减性,相同值的元素在队列中是相邻排列的。
  • 根据上述出队和入队操作的描述,我们发现在对队列进行操作时,除了规范的队列的队尾插入元素、队首删除元素和查看队首元素外,我们定义的入队操作中还包含查看队尾元素、删除队尾元素,即在队列的两端进行插入、删除等操作,可以使用双向链表。为了方便操作,我们可以将双向链表进行包装,并定义入队和出队操作。
  • 在遍历时,
  • 将前 k 个元素入队,
  • 保存当前队首元素,作为当前滑动窗口的最大值,
  • 接着继续依次遍历数组元素,在每一次循环中,先入队,再出队,再将当前队首元素作为当前滑动窗口的最大值保存,
  • 循环结束,返回最大值集合。

代码

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        myQueue<Integer> que =new myQueue<>();

        int[] maxInWindow=new int[nums.length - k +1];
        // index: 当前要出队的元素的下标
        // index + k : 当前要入队的下标
        int index=0;

        for(int i=0;i<k;i++){
            que.add(nums[i]);
        }

        maxInWindow[index] = que.front(); 

        // index + k 的最大值取为 nums.length -1,
        // 表示最后一个要入队的元素
        while(index + k < nums.length){       

            // 入队
            que.add(nums[index + k]);

            // 出队
            que.poll(nums[index]);

            index++;    

            maxInWindow[index] = que.front();      

        }

        return maxInWindow;
    }

    private class myQueue<T extends Comparable>{

        Deque<T> que;

        myQueue(){
            que= new LinkedList<>();
        }

        public void add(T x){
            while(!que.isEmpty() && que.getLast().compareTo(x) < 0){
                que.removeLast();
            }

            que.add(x);
        }

        public T front(){
            return que.peek();
        }

        public void poll(T x){
            if(!que.isEmpty() && que.peek().compareTo(x) == 0){
                que.poll();
            }
        }

    }
}

347.前 K 个高频元素

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

347. 前 K 个高频元素 - 力扣(Leetcode)

思路

  • 统计数组中各数字出现的频数,并将该数字作为键、频数作为值保存在哈希表中。
  • 遍历哈希表,将哈希表的每一项插入到优先队列中,优先队列设置为以频数进行从大到小排序的,即大顶堆
  • 依次从优先队列中取出 k个元素,将其键保存到结果集中,并返回结果集。

代码

class Solution {
    public int[] topKFrequent(int[] nums, int k) {

        Map<Integer,Integer> map = new HashMap<>();

        // 统计每种整数的频数
        for(int i=0;i<nums.length;i++){
            map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
        }

        PriorityQueue<int[]> queue = new PriorityQueue<>((pair1,pair2)->pair2[1]-pair1[1]);

        for(Map.Entry<Integer,Integer> 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()[0];
        }

        return res;


    }
}