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

题目链接:239. 滑动窗口最大值-困难

题目描述:

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

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length

思路:

单调队列

239.滑动窗口最大值

要求最大值,应该保证队列中的数是单调递减的,这样front才能保证是最大的。

一开始的思路是用单向队列queue,这样只能push(_back)pop(_front)。根据窗口滑动,每次pop窗口里的第一个数,push最后一个数到队列末尾,然后把队列里最大的数依次求出来即可。但是这样实现每次只能最后一个数跟第一个数比,无法跟中间的数比较,因此行不通。

改用双向队列deque,思路同上,不过双向队列可以保证队列中的数是单调递减的。

  • 假设现在的队列中的数是单调递减的
  • 每次遍历到一个新的数nums[i],先判断队列中的第一个数nums[i - k]是否在该滑动窗口内(这个数一定在上一个滑动窗口内,那么队列中剩下的数一定在该滑动窗口内),若不在先pop
  • nums[i]跟队列里的其他数比,若nums[i]比队列里最后一个数大,那把最后一个数扔了直至比队列里最后一个数小或者队列空了,这在单向队列里做不到而在双向队列里可以(想象一下,若k比较大,那么nums[i]必定在下一个滑动窗口内,且在下一个滑动窗口内跟该滑动窗口nums[i]前面的数的大小不用再比较了)
  • nums[i]加到队列里维持单调递减,此时队列里第一个数则为该滑动窗口最大的数
  • 注意:这种解法队列里数的个数始终≤k,因此只用一个循环无法判断第一个窗口到哪里结束,因此得先把第一个窗口的数解决掉,再用上述思路从第k个元素开始遍历。

代码如下:

// 时间复杂度:O(n)
// 空间复杂度:O(k)
class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> res;
        deque<int> dq;
        int maxForeK;
        for(int i = 0; i < k; ++i){
            while((!dq.empty()) && nums[i] > dq.back()){
                dq.pop_back();
            }
            dq.push_back(nums[i]);
        }
        res.push_back(dq.front());
        for(int i = k; i < nums.size(); ++i){
            if(!dq.empty() && nums[i - k] == dq.front()){
                dq.pop_front();
            }
            while(!dq.empty() && nums[i] > dq.back()){
                dq.pop_back();
            }
            dq.push_back(nums[i]);
            res.push_back(dq.front());
        }
        return res;
    }
};

题目链接:347. 前 K 个高频元素-中等

题目描述:

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

示例 1:

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

示例 2:

输入: nums = [1], k = 1
输出: [1]

提示:

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

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

可以直接用unordered_map保存元素,然后对value排序

20190624173156.jpg

用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。用优先级队列priority_queue完成小顶堆

注意:

大家对这个比较运算在建堆时是如何应用的,为什么左大于右就会建立小顶堆,反而建立大顶堆比较困惑。

确实 例如我们在写快排的cmp函数的时候,return left>right 就是从大到小,return left<right 就是从小到大。

优先级队列的定义正好反过来了,可能和优先级队列的源码实现有关(我没有仔细研究),我估计是底层实现上优先队列队首指向后面,队尾指向最前面的缘故!

代码如下:

// 时间复杂度:O(nlogk)
// 空间复杂度:O(n)
class Solution {
public:
	// 小顶堆,仿函数
    struct mycomparison {
        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> umap;
        vector<int> res;
        for(int i = 0; i < nums.size(); ++i){
            ++umap[nums[i]];
        }
        // 定义一个小顶堆,元素为umap的键值对
        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;
        for(auto& it : umap){
            pri_que.push(it);
            if(pri_que.size() > k) // 保证堆的大小一直为k
                pri_que.pop();
        }
        // 题目无要求按顺序,可以直接输出一个pop一个
        // 找出前K个高频元素,小顶堆先弹出的是最小的,可以for循环倒序来输出到数组
        while(!pri_que.empty()){
            res.emplace_back(pri_que.top().first);
            pri_que.pop();
        }
        return res;
    }
};
posted @ 2024-03-04 23:34  Humphreyr  阅读(5)  评论(0编辑  收藏  举报