第五章 栈与队列part03
今日内容
- 239.滑动窗口最大值
- 347.前 K 个高频元素
- 总结
详细布置
239.滑动窗口最大值 (一刷至少需要理解思路)
之前讲的都是栈的应用,这次该是队列的应用了。
本题算比较有难度的,需要自己去构造单调队列,建议先看视频来理解。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0239.%E6%BB%91%E5%8A%A8%E7%AA%97%E5%8F%A3%E6%9C%80%E5%A4%A7%E5%80%BC.html
c++:
单调队列套路
- 入(元素进入队尾,同时维护队列单调性)
- 出(元素离开队首)
- 记录/维护答案(根据队首)
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
vector<int> ans;
deque<int> q; // 双端队列
for (int i = 0; i < nums.size(); i++) {
// 1. 入
while (!q.empty() && nums[q.back()] <= nums[i]) {
q.pop_back(); // 维护 q 的单调性
}
q.push_back(i); // 入队
// 2. 出
if (i - q.front() >= k) { // 队首已经离开窗口了
q.pop_front();
}
// 3. 记录答案
if (i >= k - 1) {
// 由于队首到队尾单调递减,所以窗口最大值就是队首
ans.push_back(nums[q.front()]);
}
}
return ans;
}
};
java: 题解:
https://leetcode.cn/problems/sliding-window-maximum/solutions/2361228/239-hua-dong-chuang-kou-zui-da-zhi-dan-d-u6h0/
/**
* 计算滑动窗口中的最大值。
*
* @param nums 整数数组,代表输入数据。
* @param k 滑动窗口的大小。
* @return int[] 包含每个滑动窗口最大值的数组。
*/
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length; // 输入数组的长度
int[] res = new int[n - k + 1]; // 结果数组,长度为输入数组长度减去窗口大小加一
Deque<Integer> q = new ArrayDeque<Integer>(); // 双向队列,用于存储窗口中的元素下标
for(int i = 0;i < n;i++) {
// 从队尾移除所有小于等于当前元素的值的下标
while(!q.isEmpty() && nums[q.getLast()] <= nums[i]){
q.removeLast();
}
q.addLast(i); // 将当前元素下标添加到队尾
// 如果窗口超出指定大小,移除队首的元素下标
if(i - q.getFirst() >= k) q.removeFirst();
// 当处理到第k个元素时,将窗口中的最大值添加到结果数组中
if(i >= k - 1) res[i - k + 1] = nums[q.getFirst()];
}
return res; // 返回结果数组
}
347.前 K 个高频元素 (一刷至少需要理解思路)
大/小顶堆的应用, 在C++中就是优先级队列
本题是 大数据中取前k值 的经典思路,了解想法之后,不算难。
题目链接/文章讲解/视频讲解:https://programmercarl.com/0347.%E5%89%8DK%E4%B8%AA%E9%AB%98%E9%A2%91%E5%85%83%E7%B4%A0.html
c++:
使用哈希表统计每个数字出现的个数,然后维护一个大小为k的小顶堆即可。
class Solution {
public:
//小顶堆
class mycomparison{
public:
bool operator() (const pair<int,int> &lhs,const pair<int,int> &rhs){
//lhs rhs 为map的两个元素,lhs.first是nums里的数key,对应的lhs.second就是出现次数value。
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]]++;
}
//对频率进行排序
// 定义一个小顶堆,大小为k
priority_queue<pair<int,int>,vector<pair<int,int>>,mycomparison> q;
//遍历map中的元素
//若队列元素个数超过k,则将栈顶元素出栈
for(auto &a : map){
q.push(a);
if(q.size() > k){
q.pop();
}
}
// 找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
vector<int> res;
for(int i = k - 1;i >= 0;i--){
res.push_back(q.top().first);
q.pop();
}
return res;
}
};
算法2
(计数排序) O(n)
:计数排序。我们可以先统计每个数字出现了多少次,在统计一下出现次数为t
次的元素各有多少个,然后利用计数排序的思想判断一下出现次数前K多的数字最少出现多少次,求出这个下界,最后再遍历一次哈希表,将所有出现次数大于等于这个下界的元素加入答案。因为题目中已经表明了第k
多和第k + 1
多的元素出现次数不同,这就为我们省去了很多的边界判断。
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
int n = nums.size();
//统计次数
unordered_map<int,int> hash;
for(int x : nums) hash[x]++;
//s[i]表示出现i次的元素个数
vector<int> s(n + 1);
for(auto item : hash)
{
s[item.second]++;
}
//找到分界点t
int count = 0,t = n;
while(count < k) count += s[t--];
//计算答案
vector<int> res;
for(auto item : hash)
{
if(item.second > t)
res.push_back(item.first);
}
return res;
}
};
java:
class Solution {
public List<Integer> topKFrequent(int[] nums, int k) {
// 使用字典,统计每个元素出现的次数,元素为键,元素出现的次数为值
HashMap<Integer,Integer> map = new HashMap();
for(int num : nums){
if (map.containsKey(num)) {
map.put(num, map.get(num) + 1);
} else {
map.put(num, 1);
}
}
// 遍历map,用最小堆保存频率最大的k个元素
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer a, Integer b) {
return map.get(a) - map.get(b);
}
});
for (Integer key : map.keySet()) {
if (pq.size() < k) {
pq.add(key);
} else if (map.get(key) > map.get(pq.peek())) {
pq.remove();
pq.add(key);
}
}
// 取出最小堆中的元素
List<Integer> res = new ArrayList<>();
while (!pq.isEmpty()) {
res.add(pq.remove());
}
return res;
}
}
总结
栈与队列做一个总结吧,加油
https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E6%80%BB%E7%BB%93.html