代码随想录第13天 | 栈与队列part03

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

题目一 239. 滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。
返回滑动窗口中的最大值。
进阶:在线性时间复杂度内解决此题。

关键思路:自己创建一个子队列,维持这个队列保持单调递增或者递减。
比如:让出口处元素保持最大,后面的元素依次递减,那么就在传进元素时,不断留下更大的,抛出小的,直到后面进入队列的元素没有前者大为止。
然后如果出现了更大的元素,就直接推掉它前面的全部元素(因为之前的最大值都没有它大),然后再让后面的进来。

效果图如下所示。
队列中依次进入2,3,5,就从前面推掉2,3只留下5;
后面进入1,4,4没5大,但比1大,从后端把1推掉,5,4留在队列中;
8进来,比前面的都大(比前面最大的5还大),把前面的全推掉,队列里只剩下8.

注意:本题并不止返回最终的最大值,需要对整个过程中出现的极大值进行记录,并打包在一个数组中。数组的长度也是整个数据数组和滑动窗口组合的可能性数量,为 nums.length - (k-1).
比如数组长为8,滑动窗口长度为3,那么滑动过程中的情况共6种,因此也会出现6种局部最大值。

注意:pop的时候进行判断,如果当前要扔出的是队列最前面,也是最大的元素,会为它单独调用pop()让它出去;此时后面的元素不动。直到再次push时,再判断扔出前面的元素,留下最大的在前端。
如果新进来的元素比最前端最大的元素小,但比它本身前面的元素大,那么它将依次将这些小元素向后扔掉。

import java.util.*;

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if(nums.length == 1)
            return nums;
        int len = nums.length - (k - 1);
        int[] res = new int[len];//最终答案的数组
        Myqueue que = new Myqueue(); //队列
        int count = 0;
        for(int i=0; i<k; i++)//先放进k个然后比
        {
            que.push(nums[i]);
        }
        
        res[count] = que.Getmax();
        count++;
        for(int i=k; i<nums.length; i++)//从后面挨个塞挨个比较
        {
            que.pop( nums[i - k]);//注意这里是排掉上面循环中队列元素
            que.push( nums[i] );

            res[count] = que.Getmax();
            count++;
        }

        return res;
    }
}

class Myqueue{
    Deque<Integer> newque = new LinkedList<>();
    public void push(int val)
    {
	    
		// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
        // 这样就保持了队列里的数值是单调从大到小的了。
        while( newque.isEmpty() != true && val > newque.getLast() )//进来的值比末尾这个大
        {
            newque.removeLast();//把最后一个元素推了
        }
        newque.add(val);
    }

    public void pop(int val)
    {//队列的peek()处为最大值,也是出口,也是last端
        if( newque.isEmpty() != true && val == newque.peek() )
        {
            newque.poll();//为最大值单独调用poll
        }
    }

    public int Getmax()
    {
        return newque.peek();
    }
}

题目二 347.前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 :

  • 输入: nums = [1,1,1,2,2,3], k = 2 输出: [1,2]

本题考虑使用优先级队列或者堆(节点具有权值且按序排列的完全二叉树)来解决。

因为返回出现频率前k 高的元素,因此只需维护一个节点数为k的顶堆,不断向堆中push和pop即可。
那么是用大顶堆还是小顶堆?因为顶堆的插入从叶子节点处,而删除却只能从根节点处,所以应该采用小顶堆,不断从根处删除最小的元素,从叶子处插入最大的。

不过在这之前,还需要使用哈希表。其中 key存储数组元素值,value为对应的元素出现次数。
时间复杂度: O(nlogk)
空间复杂度: O(n)

c++版本如下:
c++版本的好处是可以重载括号(),不过和java版本一样使用了优先级队列和哈希表。

class Solution {
public:
    // 小顶堆
    class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 要统计元素出现频率
        unordered_map<int, int> map; // map<nums[i],对应出现的次数>
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }

        // 对频率排序
        // 定义一个小顶堆,大小为k
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        // 用固定大小为k的小顶堆,扫面所有频率的数值
        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { // 如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
                pri_que.pop();
            }
        }

        // 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

java版本的答案使用了优先级队列,lambda函数,HashMap以及Map.Entry,挨个理解起来和使用真的很复杂,不如c++版本的清楚明朗…

import java.util.*;

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

        //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        PriorityQueue<int[]> pq = new PriorityQueue<>( ( pair1, pair2)-> pair1[1]-pair2[1] );//lambda函数
        for( Map.Entry<Integer,Integer> entry : newmap.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;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值