代码随想录第十一天|leecode 239,347

文章:代码随想录

状态:比较难,主要理解思路

leecode 239 滑动窗口的最大值

思路:

用单调队列为最优解,用堆实现时间复杂度则为O(Nlogn)。我们始终要维护队列的头部元素始终是当前窗口的最大值,并且保持窗口内保持单调递减,比他小的元素可以直接从后面退掉。从后面退掉更容易实现。

代码:

有几种实现方式:

//方式一,自定义队列

    class Myqueue{
        Deque<Integer> deque=new LinkedList<>();
        //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
        //同时判断队列当前是否为空
        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 int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) {
            return nums;
        }
        Myqueue myqueue=new Myqueue();
        int [] result=new int[nums.length-k+1];
        int index=0;
        //先将前k的元素放入队列
        // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
        for (int i = 0; i < k; i++) {
            myqueue.add(nums[i]);

        }
        result[index++]=myqueue.peek();
        //i代表的是窗口尾部
        for (int i = k; i < nums.length; i++) {
            //窗口每移动一次,头部就要pop掉i-k+1上一个位置的值,尾部要加入下一个当前i位置的值
            myqueue.poll(nums[i-k]);
            myqueue.add(nums[i]);
            result[index++]= myqueue.peek();

        }

        return result;

    }

接下来是双端队列的单指针和双指针:

//方法二利用双端队列手动实现单调队列
    //用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
    //这是下标版本,pop的时候可以通过下标来判断判断头节点是否需要弹出.
    public int[] maxSlidingWindowD(int[] nums, int k) {
        Deque<Integer> deque=new ArrayDeque<>();
        int[] result =new int[nums.length-k+1];
        int index=0;
        //i代表的是窗口尾部
        for (int i = 0; i < nums.length; i++) {
            //1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
            //为什么if可以而不是while呢?因为后面的元素下标一定是在当前头元素之后的。
            // 如果元素下标比当前头元素小,证明这个元素先加入队列,那么当这个头元素加入进来的时候,这些比头元素小的值早就被remove了,所以不可能存在值既比当前头元素值小,下标也比他小。
            //所以不会存在错过的情况,值也是同理,只需要比对一次.但是值不能用while,因为会存在值相同的情况,那就会移除多个元素导致数值不准确.下标不会重复
            if (!deque.isEmpty() && deque.peek() < i - k + 1){deque.poll();}
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while (!deque.isEmpty() && nums[(deque.getLast())]<nums[i] ){
                deque.removeLast();
            }
            //加入下标
            deque.offer(i);
            /// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            if(i>=k-1){
                result[index++]=nums[deque.peek()];
            }
        }
        return result;
    }


    //双端队列加双指针,值版本
    public int[] maxSlidingWindowDbPointer(int[] nums, int k) {
        Deque<Integer> deque=new ArrayDeque<>();
        int[] result =new int[nums.length-k+1];
        int index=0;
        //i代表的是窗口尾部,j代表窗口头部
        for (int i = 0,j=i-k+1; i < nums.length; i++,j++) {
            //1.判断队列头部的元素是否是当前窗口头部上一个位置的值,如果是则证明需要弹出.如果不是则证明当前窗口的值是后面的值,现阶段无需弹出
            //和之前自定义队列的弹出相同
            if (!deque.isEmpty() && j>0 && deque.peek() ==nums[j-1]){deque.poll();}
            // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
            while (!deque.isEmpty() && deque.getLast()<nums[i] ){
                deque.removeLast();
            }
            //加入值
            deque.offer(nums[i]);
            /// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
            //j等于0时候,第一个k已经遍历完
            if(j>=0){
                result[index++]=deque.peek();
            }
        }
        return result;
    }

Leecode 347 前k个高频元素

思路:

这个主要是要知道用堆来实现.那么可以改变优先队列中的对比器来用大顶堆或者小顶堆来实现。

代码:


    //小顶堆实现.小顶堆的思路是,只保留最大的前k个元素,每次添加时将这k个中最小的元素弹出.
    public int[] topKFrequent2(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数。所以数组中第一个元素代表数组的值,第二个元素代表了该值出现的次数.
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
//        PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
//            @Override
//            public int compare(int[] o1, int[] o2) {
//                return o1[1]-o2[1];
//            }
//        });
        //lambda表达式写法
//        PriorityQueue<int[]> pq = new PriorityQueue<>((int[]a,int[]b)->{return a[1]-b[1];});
        PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[1]-b[1]);
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
            if(pq.size()<k){//小顶堆元素个数小于k个时直接加
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                if(entry.getValue()>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];
        }
        return ans;
    }
    //解法2:基于大顶堆实现 大顶堆的思路是把所有元素排序然后只pop前k个.
    public int[] topKFrequent1(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
        for(int num:nums){
            map.put(num,map.getOrDefault(num,0)+1);
        }
        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){//大顶堆需要对所有元素进行排序
            pq.add(new int[]{entry.getKey(),entry.getValue()});
        }
        int[] ans = new int[k];
        for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
            ans[i] = pq.poll()[0];
        }
        return ans;
    }

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值