代码随想录 10.04 || 栈与队列 LeetCode 239.滑动窗口的最大值、347.前 k 个高频元素

239.滑动窗口的最大值(双指针)

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


                            输入: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

        取步幅为 1 的、感受野为 k 的滑动窗口内的元素最大值。本题在卡哥的代码随想录中归类在栈和队列章节中,本人对于栈和数据结构的掌握程度还不熟练,读完第一感觉是双指针(三指针)解法,左 left 指针和右 right 指针维护一个 k 大小的滑动窗口,最大值 peak 指针找当前窗口内的最大值。为了区别暴力解法(嵌套循环),我们对窗口的滑动机制做出优化。滑动窗口的步幅为 1,假设当前为第 i 次循环,此时的 peak 和 peak 指向的元素有以下几种情况:

        1. 第 i 次的 peak 指向的值大于第 i+1 次的右边界元素,但是 peak 指向 left 的位置,此时的 peak 不在滑动窗口的感受野内,无效。重新在当前窗口内找最大值,这是最坏的情况,和嵌套循环没有区别;

        2. 第 i 次的 peak 指向的值大于第 i+1 次的右边界元素, 且 peak 指向非 left 的位置,此时的 peak 也是 i+1 次滑动窗口内的最大值;

        3. 第 i 次的 peak 指向的值小于第 i+1 次的右边界元素,那么此时右边界元素就是该窗口内的最大元素,和 peak 的位置无关;

        可以看出,最坏的情况一的时间复杂度为O(n*k),就是数组为降序数组,最大值一直出现在滑动窗口的左边界的情况下。最好的情二和三,只需要一次判断即可。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        int left = 0, peak = 0;

        for (int i = 0; i < k; i++) {
            if (nums[i] > nums[peak]) {
                peak = i;
            }
        }
        result.push_back(nums[peak]);

        for (int right = k; right < nums.size(); right++) {
            if (nums[peak] > nums[right] && peak > left) {
                result.push_back(nums[peak]);
            } else if (nums[peak] < nums[right]) {
                peak = right;
                result.push_back(nums[peak]);
            } else {
                peak = left + 1;
                for (int i = left + 2; i <= right; i++) {
                    if (nums[i] > nums[peak]) {
                        peak = i;
                    }
                }
                result.push_back(nums[peak]);
            }
            left++;
        }
        return result;
    }
};

239.滑动窗口的最大值(单调双向队列)

        在卡哥的代码随想录里,自定义了一个单调的双向队列来遍历这个整形数组,以返回每个滑动窗口内的最大值。在队列中,实现了 pop、push 和返回队首元素接口。该队列中,仅维护可能成为最大值的元素,每当滑动窗口移出的元素和队首元素相同时,将队首元素出队;再向队尾添加一个元素,在 push 元素的过程中,需要判定,如果小于队列中的元素,则正常 push,如果大于队列中的元素,则将前面的元素清空,总要维持,队列中的元素为最大的。这样每一次取队首,就是取当前滑动窗口内的最大值。

class Solution {
public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        vector<int> result;
        int left = 0, peak = 0;

        for (int i = 0; i < k; i++) {
            if (nums[i] > nums[peak]) {
                peak = i;
            }
        }
        result.push_back(nums[peak]);

        for (int right = k; right < nums.size(); right++) {
            if (nums[peak] > nums[right] && peak > left) {
                result.push_back(nums[peak]);
            } else if (nums[peak] < nums[right]) {
                peak = right;
                result.push_back(nums[peak]);
            } else {
                peak = left + 1;
                for (int i = left + 2; i <= right; i++) {
                    if (nums[i] > nums[peak]) {
                        peak = i;
                    }
                }
                result.push_back(nums[peak]);
            }
            left++;
        }
        return result;
    }
};

347.前 k 个高频元素

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

        第一反应使用哈希表,得到各个数字出现的频率,然后对频率进行排序,最后输出前 k 个元素即可。但是,如何高效地进行排序?快排?可以使用优先队列(小根堆),维护一个 k 大小的小根堆,将频率小的元素都弹出去,最后保留前 k 个频率最高的。因为不太懂优先队列和小根堆,笔至于此。

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;
    }
};

        总体思想不难,了解一下优先队列和小根堆就好。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值