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

滑动窗口最大值

暴力解法

        会超时,计算每个窗口内最大值,并将其加入最后的窗口最大值数组。这里我有使用一些剪枝,但仍会超时。具体代码如下。算法的时间复杂度为O(n*k),空间复杂度为O(n-k+1),简化为O(n)。

class Solution {
public:
    // 辅助函数,用于找到从begin到end索引范围内的最大值
    int find_max(vector<int>& nums, int begin, int end) {
        int max = -10000; // 初始化最大值为一个很小的数
        for (int i = begin; i <= end; i++) { // 遍历begin到end之间的所有元素
            if (nums[i] > max) { // 如果当前元素大于max,则更新max
                max = nums[i];
            }
        }
        return max; // 返回最大值
    }

    // 主函数,用于求解滑动窗口的最大值序列
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans; // 创建一个空向量,用于存储每个窗口的最大值
        int left = 0; // 初始化窗口的左边界
        int right = k - 1; // 初始化窗口的右边界(k-1是初始窗口的最后一个元素的索引)
        int max = find_max(nums, left, right); // 找到初始窗口的最大值
        for (int i = 0; i <= nums.size() - k; i++) { // 遍历所有可能的窗口
            if (left == 0) { // 如果是第一个窗口,直接将最大值加入答案
                ans.push_back(max);
                left++; // 窗口左边界向右移动
                right++; // 窗口右边界向右移动
            }
            else if (left > 0 && nums[left - 1] != max && nums[right] < max) { // 如果左边界不是第一个元素,且离开的元素不是最大值,且新加入的元素小于最大值
                ans.push_back(max); // 保持最大值不变,加入答案
                left++; // 窗口左边界向右移动
                right++; // 窗口右边界向右移动
            }
            else { // 其他情况,需要重新找到当前窗口的最大值
                max = find_max(nums, left, right); // 重新找到当前窗口的最大值
                ans.push_back(max); // 将最大值加入答案
                left++; // 窗口左边界向右移动
                right++; // 窗口右边界向右移动
            }
        }
        return ans; // 返回最大值序列
    }
};

单调队列

单调队列 滑动窗口最大值【基础算法精讲 27】_哔哩哔哩_bilibiliicon-default.png?t=N7T8https://www.bilibili.com/video/BV1bM411X72E/?spm_id_from=333.337.search-card.all.click&vd_source=fc4a6e70e3a87b7ea67c2024e326e7c5这个视频里对飞机在飞机上从上往下看找高山的例子我感觉很形象。

这里寻找最大值,将可能的最大值存入双端队列中,每次移动都先判断其是否有可能成为最大值,即若队列非空,它比队列中队首的数小(因为队首的数在下一次移动会弹出),但比它入队前的队尾要大,则将队尾弹出,将它入队,形成一个有序的双端队列。

我们需要一个数据结构,能够实现(双端队列)

  • 移除最左边的元素
  • 移除最右边的元素
  • 在最右边插入元素

以及(单调性)

  • 从队首到队尾单调递减

即一个单调队列。代码如下。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> ans; // 存储结果的数组
        deque<int> dq; // 双端队列,用于存储可能的最大值候选

        for (int i = 0; i < nums.size(); ++i) {
            // 移除窗口之外的元素索引
            while (!dq.empty() && dq.front() < i - k + 1) {
                dq.pop_front();
            }
            // 移除所有小于当前元素的索引,保证队列是单调递减的
            while (!dq.empty() && nums[dq.back()] < nums[i]) {
                dq.pop_back();
            }
            // 将当前元素索引加入队列
            dq.push_back(i);
            // 当窗口形成时,将最大值加入结果数组
            if (i >= k - 1) {
                ans.push_back(nums[dq.front()]);
            }
        }

        return ans;
    }
};

算法的时间复杂度为O(n),空间复杂度为O(k)。

前K个高频元素

哈希表加快排

        首先创建了一个 unordered_map 来存储每个元素及其出现的频率。然后,将 map 中的键值对复制到一个 vector 中。接下来,我们使用 sort 函数对 vector 进行排序,排序的依据是键值对的第二个元素(即频率),排序完成后,我们创建了一个 vector ans 来存储前 k 个高频元素的键。我们遍历排序后的 vector,将前 k 个元素的键放入 ans 中。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 创建一个哈希map来存储元素及其出现频数
        unordered_map<int, int> freq;
        for (int num : nums) {
            freq[num]++;
        }

        // 将map中的键值对存储到vector中
        vector<pair<int, int>> freqVec(freq.begin(), freq.end());

        // 根据频率对键值对进行排序
        sort(freqVec.begin(), freqVec.end(), [](const pair<int, int>& a, const pair<int, int>& b) {
            return a.second > b.second; // 按照频率降序排序
        });

        // 创建答案数组
        vector<int> ans(k);
        // 将前k个高频元素的键放入答案数组
        for (int i = 0; i < k; ++i) {
            ans[i] = freqVec[i].first;
        }

        return ans;
    }
};

算法的时间复杂度为O(nlogn),空间复杂度为O(n)。

哈希表加最小堆

        我们首先使用unordered_map来统计每个元素的出现频率。然后,利用一个最小堆来维护频率最高的k个元素。每次从哈希表中取出一个元素,就将其插入到堆中,如果堆的大小超过了k,我们就弹出堆顶元素,这样堆中始终维护的是频率最高的k个元素。最后,我们将堆中的元素取出到答案数组中,由于堆是最小堆,所以我们需要从后往前填充数组,以确保数组是按照频率从高到低排序的。

class Solution {
public:
    vector<int> topKFrequent(vector<int>& nums, int k) {
        // 创建一个哈希map来存储元素及其出现频数
        unordered_map<int, int> freq;
        for (int num : nums) {
            freq[num]++;
        }

        // 创建一个最小堆,用于存储频率最高的k个元素
        // 堆中的元素是pair,first是频率,second是元素值
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> minHeap;

        // 遍历哈希表,将元素插入堆中
        for (auto& pair : freq) {
            minHeap.push(make_pair(pair.second, pair.first));
            // 如果堆的大小超过k,弹出堆顶元素(频率最小的)
            if (minHeap.size() > k) {
                minHeap.pop();
            }
        }

        // 从堆中取出元素到答案数组中
        vector<int> ans(k);
        for (int i = k - 1; i >= 0; --i) {
            ans[i] = minHeap.top().second;
            minHeap.pop();
        }

        return ans;
    }
};

时间复杂度O(nlogk),k为堆的大小,空间复杂度O(n)

  • 9
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值