随想录日记part11
t i m e : time: time: 2024.03.04
主要内容:今天的主要内容是深入了解栈和队列中比较难的题录类型:滑动窗口最大值与前 K K K 个高频元素,最后对于这三天学习的队列和栈的知识进行总结。
- 239. 滑动窗口最大值
- 347.前 K 个高频元素
- 总结
Topic1滑动窗口最大值
题目:
给你一个整数数组
n
u
m
s
nums
nums,有一个大小为
k
k
k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的
k
k
k 个数字。滑动窗口每次只向右移动一位。返回滑动窗口中的最大值 。
示例:
输入:
n
u
m
s
=
[
1
,
3
,
−
1
,
−
3
,
5
,
3
,
6
,
7
]
,
k
=
3
nums = [1,3,-1,-3,5,3,6,7], k = 3
nums=[1,3,−1,−3,5,3,6,7],k=3
输出:
[
3
,
3
,
5
,
5
,
6
,
7
]
[3,3,5,5,6,7]
[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 |
思路: 使用单调队列是本题主要的思路:难点是如何求一个区间里的最大值
为了实现实现上述目标,因此需要创建出一个这样的队列:放进去窗口里的元素,然后随着窗口的移动,队列也一进一出,每次移动之后,队列告诉我们里面的最大值是:
class MyQueue {
public:
void pop(int value) {
}
void push(int value) {
}
int front() {
return que.front();
}
};
每次窗口移动的时候,调用 q u e . p o p que.pop que.pop(滑动窗口中移除元素的数值), q u e . p u s h que.push que.push(滑动窗口添加元素的数值),然后 q u e . f r o n t ( ) que.front() que.front() 就返回我们要的最大值
其实队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。看看下面的动画演示:
此时单调队列里维护着 { 5 , 4 } \{5, 4\} {5,4} 配合窗口进行滑动要保持如下规则:
- p o p ( v a l u e ) pop(value) pop(value):如果窗口移除的元素 v a l u e value value 等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- p u s h ( v a l u e ) push(value) push(value):如果 p u s h push push 的元素 v a l u e value value 大于入口元素的数值,那么就将队列入口的元素弹出,直到 p u s h push push 元素的数值小于等于队列入口元素的数值为止
java实现的代码如下:
//自定义单调队列
class Myqueue {
Deque<Integer> deque = new LinkedList<>();
// 窗口移除的元素 value 等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
void poll(int a) {
if (!deque.isEmpty() && a == deque.peek()) {
deque.poll();
}
}
// 如果 add 的元素 value 大于入口元素的数值,那么就将队列入口的元素弹出,直到 add 元素的数值小于等于队列入口元素的数值为止
void add(int value) {
while (!deque.isEmpty() && value > deque.getLast()) {
deque.removeLast();
}
deque.add(value);
}
返回队列最大值
int peek() {
return deque.peek();
}
}
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
int length = nums.length;
if (length == 1)
return nums;
// 记录最后输出的数组长度
int l = length - k + 1;
int[] zu = new int[l];
Myqueue queue = new Myqueue();
for (int i = 0; i < k; i++) {
queue.add(nums[i]);
}
int count = 0;
zu[count++] = queue.peek();
for (int i = k; i < length; i++) {
queue.poll(nums[i - k]);
queue.add(nums[i]);
zu[count++] = queue.peek();
}
return zu;
}
}
时间复杂度:
O
(
n
)
O(n)
O(n)
空间复杂度:
O
(
k
)
O(k)
O(k)
Topic2前 K K K 个高频元素
题目:给你一个整数数组 n u m s nums nums 和一个整数 k k k ,请你返回其中出现频率前 k k k 高的元素。你可以按任意顺序返回答案。
示例:
输入:
n
u
m
s
=
[
1
,
1
,
1
,
2
,
2
,
3
]
,
k
=
2
nums = [1,1,1,2,2,3], k = 2
nums=[1,1,1,2,2,3],k=2
输出:
[
1
,
2
]
[1,2]
[1,2]
思路:
- 要统计元素出现频率-----> M a p Map Map 实现
- 对频率排序----->优先级序列
- 找出前K个高频元素
java实现的代码如下:
/*Comparator接口说明:
* 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
* 对于队列:排在前面意味着往队头靠
* 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
* 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
* */
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//key为数组元素值,val为对应出现次数
Map<Integer,Integer> map=new HashMap<>();
for( int num:nums){
//计算数字出现的频率
map.put(num,map.getOrDefault(num,0)+1);
}
//出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
pq.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
int[] tem=new int[k];
for(int i=k-1;i>-1;i--){
tem[i]=pq.poll()[0];
}
return tem;
}
}
这上面的代码不熟悉
时间复杂度: O ( n l o g k ) O(n\ logk) O(n logk)
空间复杂度: O ( n ) O(n) O(n)
Topic3栈和队列总结:
1.栈里面的元素在内存中是连续分布的么?
- 陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中不一定是连续分布的。
- 陷阱2:缺省情况下,默认底层容器是 d e q u e deque deque,那么 d