代码随想录算法训练营第十三天-栈和队列3| 239. 滑动窗口最大值 347.前 K 个高频元素

之前讲的都是栈的应用,这次该是队列的应用

239. 滑动窗口最大值 (一刷至少需要理解思路)

本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。

单调队列-自己实现一个队列

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

//自定义单调队列
class MyQueue{
    Deque deque = new LinkedList<>();
    //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出(=max弹出)
    //同时判断队列当前是否为空
    void poll(int val){
        if(!deque.isEmpty() && val == deque.peek()){
            deque.poll();
        }
    }
    //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
    //保证队列元素单调递减
    //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
    void add(int val){
        while (!deque.isEmpty() && val > deque.getLast()){
            deque.removeLast();
        }
        deque.add(val);
    }
    //队列队顶元素始终为最大值
    int peek(){
        return deque.peek();
    }

}
//主函数
public class SlidingWindowMaximum {

    public int[] maxSlidingWindow(int[] nums, int k){
        if(nums.length == 1){
            return nums;
        }
        int len = nums.length - k + 1;// n - k + 1是滑块要滑动的次数
        //存放结果元素的数组
        int[] res = new int[len];
        int index =0;


        //自定义队列
        MyQueue myQueue = new MyQueue();
        //先将前k的元素放入队列
        for(int i = 0; i < k; i++){
            myQueue.add(nums[i]);
        }
        res[index++]= myQueue.peek();
        // 遍历数组
        // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
        // 队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
        for(int i= k; i < nums.length; i++){
            //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
            myQueue.poll(nums[i - k]);
            //滑动窗口加入最后面的元素
            myQueue.add(nums[i]);
            //记录对应的最大值
            res[index++] = myQueue.peek();
        }
        return res;

    }
}

347.前 K 个高频元素 (一刷至少需要理解思路)

1.堆(小顶堆)
思路(定义变量)
一个map来记录数组中值的出现频率
一个heap小顶堆,来对于最大的前K个元素排序
一个int[ ],用来接收数组中前k个最大的值


2. 本题思路分析:
通过map记录统计数组中各个元素的出现频率
一个heap小顶堆,来对于最大的前K个元素排序,
遍历数组中的所有元素,当小顶堆不满K个元素时,将数组当前元素直接加入堆中
当小顶堆存满K个元素后,每次加入元素都与堆顶元素作比较,大于堆顶元素将堆顶元素弹出堆,加入当前元素(小顶堆最小的元素在堆顶)
当数组便利完毕后,依次弹出堆顶元素,此时即为从小到大输出数组前K个最大的值,所以可以将数组从后往前接收堆弹出的值

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

public class TopKFrequentElements {

    /*Comparator接口说明:
     * 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
     * 对于队列:排在前面意味着往队头靠
     * 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
     *                                从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
     * */

    //解法2:基于小顶堆实现
    public int[] topKFrequent2(int[] nums, int k){
        Map map = new HashMap<>();//key为数组元素值,val为对应出现次数
        for(int num : nums){
            //getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。
            //map统计频率,key对应value从0开始,默认value++
            map.put(num, map.getOrDefault(num,0)+ 1);
        }
        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        PriorityQueue pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);//实现compare函数
        //for遍历map
        for (Map.Entry entry:map.entrySet()){
            //小顶堆只需要维持k个元素有序
            if(pq.size()pq.peek()[1]){
                    //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
                    pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
                    pq.add(new int[]{entry.getKey(), entry.getValue()});
                }
            }
        }
        int[] ans = new int[k];//定义一个返回数组
        for(int i = k-1; i>=0;i--){//倒序遍历数组
            //依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
            ans[i] = pq.poll()[0];//需要弹出是key
        }
        return ans;//返回结果
    }

}
class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map map = new HashMap<>();
        for(int num: nums){
            map.put(num,map.getOrDefault(num,0)+ 1);
        }

        PriorityQueue pq = new PriorityQueue<>((pair1,pair2)->(pair1[1]-pair2[1]));
        // Using entrySet() to get the entry's of the map
        Set> s = map.entrySet();
        for(Map.Entry entry: s){
            // Using the getKey to get key of the it element
            // Using the getValue to get value of the it element
            if(pq.size()< k){
                //堆里保持>k个元素
                pq.add(new int[]{entry.getKey(), entry.getValue()});
            }else{
//保持要加入元素大于栈顶元素=证明小顶堆
                if(entry.getValue() > pq.peek()[1]){
                    pq.poll();
                    pq.add(new int[]{entry.getKey(), entry.getValue()});
                }
            }
        }
        int[] ans = new int[k];
        for(int i = k -1; i>=0; i--){
            ans[i] = pq.poll()[0];
        }
        return ans;
    }
}
Console
//getOrDefault() 方法获取指定 key 对应对 value,如果找不到 key ,则返回设置的默认值。

import java.util.*;
 
public class Test {
    public static void main(String[] args) {
        HashMap map = new HashMap();
        
        map.put("张三", 1);
 
        int a = map.getOrDefault("张三", 666);
        int b = map.getOrDefault("王五", 666);
        System.out.println(a);
        System.out.println(b);
    }
}

张三是已经存在的键值,返回了他对应的1(Value)

王五并不在map中存在,返回了默认值666(Value)

扩展:PriorityQueue
基本使用
默认:最小堆,每次可获得最小元素

优先队列按照其作用不同,可以分为以下两种:

最大优先队列: 可以获取并删除队列中最大的值
最小优先队列: 可以获取并删除队列中最小的值
将元素放入队列:add,offer
将队首元素从队列删除:remove,poll
查看队列内的对首元素:element,peek
123

和标准队列不同的是,当删除队首元素的时候,删除的是priority queue中最小的元素。但是,priority queue并不是对所有的元素排序,其内部是用heap(堆)实现的。堆是一个自组织的二叉树,在这个二叉树里,add和remove操作使得最小的元素“吸引”到二叉树的根部,而不用在排列整个队列上耗费时间。

PriorityQueue heap = new PriorityQueue<>(
                (n1,n2) -> n1-n2  //这一句加不加结果是一样的
        );
        heap.add(4);
        heap.add(1);
        heap.add(2);
        heap.add(3);
        while(!heap.isEmpty())
        {
            System.out.println(heap.poll());
        }

123456789101112
输出顺序是1,2,3,4。
可以看到默认的比较规则就是n1-n2,如果我们改成n2-n1

PriorityQueue heap = new PriorityQueue<>(
    (n1,n2) -> n2-n1 //这一句加不加结果是一样的
);
123

输出顺序就是4,3,2,1。

可以想见优先队列内部是按照比较器返回的结果进行处理的,一般认为a,b比较结果大于0,就是a大于b,等于0就是a等于b;小于0就是a小于b。
现在我们知道当比较n1,n2的时候,如果结果大于0(也就是默认的n1-n2>0),那么要把n1下沉(默认的小根堆),当我们改成n2-n1的时候,结果大于0,证明n2大于n1,但还是n1(值较小的元素)下沉,于是这个堆就成了大根堆!

代码随想录算法训练营第13天 | LeetCode 239. 滑动窗口最大值,347.前 K 个高频元素,总结_XnyuDo的博客-CSDN博客

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