代码随想录算法训练营第十一天|239.滑动窗口的最大值,347.前k个高频元素

文章介绍了如何使用单调队列和小顶堆数据结构解决滑动窗口中的最大值问题,以及如何利用哈希表统计并找出出现频率前k的元素。C++和Python代码示例展示了这两种方法的具体实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

栈与列表part03 

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

通过使用一个特殊的数据结构——单调队列(MyQueue),来高效地找到每个滑动窗口的最大值。单调队列是一个队列,但它在插入元素时保持元素的单调递减顺序。这样,队列的前端始终是当前窗口的最大值。

MyQueue类的实现细节

  • deque<int> que:使用双端队列(deque)作为底层数据结构来实现单调队列。
  • void pop(int value):当滑动窗口移动时,如果队列的前端元素是被移除的元素(即滑出窗口的元素),则将其从队列中弹出。
  • void push(int value):在添加一个新元素时,从队列的尾部开始移除所有小于新元素的值,以保持队列的单调递减性。然后将新元素加入队列尾部。
  • int front():返回队列前端的元素,即当前窗口的最大值。

maxSlidingWindow函数的工作流程

  1. 初始化:创建一个MyQueue的实例que和一个用于存储结果的向量result
  2. 填充初始窗口:遍历数组nums的前k个元素,使用que.push(nums[i])将它们加入单调队列。之后,将队列前端的元素(即最大值)添加到result中。
  3. 遍历数组:从k开始遍历nums直到末尾,每次循环:
    • 移除滑动窗口的旧前端元素,即que.pop(nums[i - k])
    • 将新的元素nums[i]加入到单调队列中,即que.push(nums[i])
    • 将当前窗口的最大值(即que.front())添加到result向量中。

 C++

class Solution {
private:
    class MyQueue { //单调队列(从大到小)
    public:
        deque<int> que; // 使用deque来实现单调队列
        void pop(int value) {
            if (!que.empty() && value == que.front()) {
                que.pop_front();
            }
        }
        void push(int value) {
            while (!que.empty() && value > que.back()) {
                que.pop_back();
            }
            que.push_back(value);
        }
        int front() {
            return que.front();
        }
    };
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
    MyQueue que;
    vector<int> result;
    for (int i = 0; i < k; i++) { // 先将前k的元素放进队列
        que.push(nums[i]);
    }
    result.push_back(que.front()); // result 记录前k的元素的最大值
    for (int i = k; i < nums.size(); i++) {
        que.pop(nums[i - k]); // 滑动窗口移除最前面元素
        que.push(nums[i]); // 滑动窗口前加入最后面的元素
        result.push_back(que.front()); // 记录对应的最大值
    }
    return result;
}
};

python

class Solution:
    from collections import deque
    class MyQueue:  # 单调队列(从大到小)
        def __init__(self):
            self.que = deque()
        
        def pop(self, value):
            if self.que and value == self.que[0]:
                self.que.popleft()
                
        def push(self, value):
            while self.que and value > self.que[-1]:
                self.que.pop()
            self.que.append(value)
        
        def front(self):
            return self.que[0]
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        que = self.MyQueue()
        result = []
        for i in range(k):  # 先将前k的元素放进队列
            que.push(nums[i])
        result.append(que.front())  # result记录前k的元素的最大值
        
        for i in range(k, len(nums)):
            que.pop(nums[i-k])  # 滑动窗口移除最前面元素
            que.push(nums[i])  # 滑动窗口加入最后面的元素
            result.append(que.front())  # 记录对应的最大值
            
        return result

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 是数组大小。

主要通过使用哈希表来统计元素的出现次数,然后利用一个小顶堆(最小堆)来找出出现频率最高的k个元素。

统计元素出现频率

  • 使用unordered_map<int, int> map;来存储数组中每个元素及其出现的次数。遍历数组nums,对于每个元素nums[i],在哈希表中增加其计数。

小顶堆的定义和使用

  • 定义了一个小顶堆priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;,其中pair<int, int>存储元素及其频率,mycomparison是一个自定义的比较函数类,用来确保堆是按元素频率的升序排列(即堆顶是频率最小的元素)。

构建小顶堆

  • 遍历哈希表,将元素及其频率作为一个pair插入小顶堆中。当堆的大小超过k时,将堆顶元素(即频率最小的元素)弹出,以保证堆中始终存储着频率最高的k个元素。

提取结果

  • 从小顶堆中提取出k个元素,并将它们的值(pair的第一个元素,即原数组中的元素)存储到结果数组result中。由于小顶堆是按频率升序排列的,因此从堆中依次弹出元素时,需要逆序填充到result数组中,以确保结果数组是按频率降序排列的。

时间复杂度分析

  • 统计频率的时间复杂度是O(n),其中n是数组nums的长度。
  • 构建和维护大小为k的小顶堆的时间复杂度是O(nlogk),因为每次插入堆的操作是O(logk),总共需要进行n次插入操作。
class mycomparison {
    public:
        bool operator()(const pair<int, int>& lhs, const pair<int, int>& rhs) {
            return lhs.second > rhs.second;
        }
    };

这段代码定义了一个函数对象(也称为仿函数)operator(),用于比较两个pair<int, int>类型的对象。它是在一个自定义比较类mycomparison中定义的,目的是为了提供给priority_queue或其他需要自定义排序准则的标准库容器使用。

  • 参数:这个运算符重载函数接受两个参数lhs(左手边的对象)和rhs(右手边的对象),每个参数都是一个pair<int, int>类型的对象。在这个pair中,first成员表示元素值,而second成员表示与该元素值相关联的频率或其他度量值。

  • 返回值:函数返回一个布尔值。如果lhssecond成员大于rhssecond成员,则返回true;否则返回false。这意味着,如果左侧元素的频率(或度量值)大于右侧元素的频率,则函数返回true

这个函数对象在priority_queue中的作用是定义元素的排序准则。具体到这个场景,它定义了一个最小堆(小顶堆)的排序准则,因为priority_queue默认是最大堆(大顶堆),而这个函数通过返回lhs.second > rhs.second实现了元素按照second成员的升序排列。因此,在这个最小堆中,堆顶元素是所有元素中second成员(即频率或度量值)最小的元素。

C++

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; 
        for (int i = 0; i < nums.size(); i++) {
            map[nums[i]]++;
        }


        priority_queue<pair<int, int>, vector<pair<int, int>>, mycomparison> pri_que;

        for (unordered_map<int, int>::iterator it = map.begin(); it != map.end(); it++) {
            pri_que.push(*it);
            if (pri_que.size() > k) { 
                pri_que.pop();
            }
        }

        vector<int> result(k);
        for (int i = k - 1; i >= 0; i--) {
            result[i] = pri_que.top().first;
            pri_que.pop();
        }
        return result;

    }
};

在Python中,实现类似功能的方法通常不需要定义像C++那样的比较类,因为Python的heapq模块(提供了堆队列算法,即优先队列算法)允许直接通过元组的自然排序能力来实现自定义排序。

python

class Solution:
    import heapq
    from collections import Counter
    def topKFrequent(self, nums: List[int], k: int) -> List[int]:
        # 使用Counter来统计每个元素出现的频率
        freq_map = Counter(nums)
        
        # 构建一个最小堆,堆中的元素是一个元组(freq, num),freq是元素出现的频率,num是元素本身
        # 由于Python的heapq是最小堆,而我们需要根据频率的最大值来弹出元素,所以我们使用频率的负值来实现这一点
        min_heap = []
        for num, freq in freq_map.items():
            heapq.heappush(min_heap, (freq, num))
            # 如果堆的大小超过了k,则弹出堆顶元素(即频率最小的元素)
            if len(min_heap) > k:
                heapq.heappop(min_heap)
        
        # 从最小堆中提取前k个高频元素
        top_k = [num for freq, num in min_heap]
        return top_k

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值