LeetCode239.滑动窗口最大值
题目描述:
给你一个整数数组 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
解题思路:
·这道题能写出来完全是看题解写出来的,可以使用队列进行求解,放进窗口里的元素,随着窗口的移动,队列也一进一出。我们使用的队列一定要包含这三个函数,如下:
class MyQueue{
public:
void pop(int value){//移除数值
}
void push(int value){//添加数值
}
int front(){
return que.front();//获得队列头元素也就是最大值
}
};
*每当窗口移动的时候,调用que.pop,que.push(),然后que.front()就返回我们的最大值根据题目描述,我们知道最终结果中元素都是排序的,而且要最大值放在出对口,用于return
·说了那么多,还没说如何实现排完序后的队列,怎么样能把窗口要移除的元素弹出呢?其实不需要纠结,不需要在意所有的元素,只要找到可能成为窗口中最大值的元素即可(因为窗口在滑动),同时保证队列里的元素数值是由大到小的。这种特殊的队列就是单调队列(可递增也可递减)。C++中我们需要自己定义以及使用条件约束实现单调队列。
·在设计单调队列的时候,pop和push操作需要保持以下操作:
1.pop(value):如果窗口移除的元素value等于单调队列的出口氧元素,那么队列弹出元素,否则不用任何操作
2.push(value):如果push的元素value大于入口元素的数值,那么久将队列入口的元素弹出,知道push元素的数值小于等于队列入口元素的数值为止
按照这两个规则,每次移动窗口的时候,只用访问que.front()就可以返回当前窗口的最大值
*特别注意这道题中,需要使用deque实现单调队列,常用的queue在没有指定容器的情况下,deque就是默认底层容器。
代码如下:
class Solution {
private:
class Myqueue{//实现单调队列
public:
deque que;
void pop(int value){
if(!que.empty() && que.front() == value){//pop之前判断队列当前是否为空
//若每次窗口弹出的数值等于队列出口元素的数组则弹出
que.pop_front();
}
}
void push(int value){//如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,知道push的数值小于等于队列入口元素的数值为止,这样就可以保持队列里的数值是单调的
while(!que.empty() && value > que.back()){
que.pop_back();
}
que.push_back(value);
}
int front(){
return que.front();
}
};
public:
vector maxSlidingWindow(vector& nums, int k) {
Myqueue que;
vector 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;
}
};
·时间复杂度:O(n)
·空间复杂度:O(k)
难点
·对单调队列的理解和实现
总结
有很多同学在求解的时候,会觉得难点是如何求一个区间的最大值。很多人会使用暴力求解,但是时间复杂度很高,说实话,在没看你题解之前,我也不知道可以使用队列进行求解,做题就是需要多见识不曾见过的题目类型或者解题方法。
LeetCode347.前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
个高频元素的集合是唯一的解题思路:
·这道题主要需要求解出以下三块内容:
1.统计元素出现频率
2.对频率排序
3.找出前K个高频元素
·首先按统计元素出现的频率,这一类的问题可以使用map来进行统计。然后对频率进行排序,这里需要使用优先级队列进行求解。优先级队列其实就是一个披着队列外衣的堆,因为优先级队列对外接口只从队头取元素,从队尾添加元素,再无其他取元素的方式。而且优先级队列内部元素是自动依照元素的权值排列。
·在缺省情况下,优先级队列利用大顶堆完成对元素的排序,这个大顶堆是以vector为表现形式的完全二叉树。
·在这道题中,我们使用优先级队列来对部分频率进行排序,使用小顶堆(父亲结点小于左右孩子),因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素怒
代码如下:
class Solution {
public:
class mycomprison{//创建小顶堆
public:
bool operator()(const pair& lhs,const pair& rhs){
return lhs.second > rhs.second;
}
};
vector topKFrequent(vector& nums, int k) {
unordered_map map;//map
for(int i = 0;i < nums.size();i++){
map[nums[i]]++;
}
//第一个参数表示队列中元素的类型,即由两个int类型组成的pair
//第二个参数表示使用vector作为底层容器存储队列中的元素
//第三个参数表示作为元素的比较方式
priority_queue,vector>,mycomprison> pir_que;
for(unordered_map::iterator it = map.begin();it != map.end();it++){//遍历小顶堆中的所有元素
pir_que.push(*it);
if(pir_que.size() > k){//如果堆的大小大于了k,则队列弹出,保证堆的大小一直为k
pir_que.pop();
}
}
vector result(k);
for(int i = k-1;i >= 0 ;i--){//找出前K个高频元素,因为小顶堆先弹出的是最小的,所以使用倒序输出数组
result[i] = pir_que.top().first;
pir_que.pop();
}
return result;
}
};
·时间复杂度:O(nlonk)
·空间复杂度:O(n)
难点:
·和上一题一样难的是对题目的理解
·对使用小顶堆的理解
·对优先级队列的理解
总结:要多做没见过的题目类型(虽然现在还没做多少),要将每一题套用合适的数据结构,这样才能简单、快速、高效的解决问题,如果没有看题解,这道题我就要使用暴力求解了,题解的思维确实特别巧妙。