239.滑动窗口最大值 文章:7. 滑动窗口最大值
题目:239. 滑动窗口最大值
视频:单调队列正式登场!| LeetCode:239. 滑动窗口最大值_哔哩哔哩_bilibili
难点在于:如何求滑动窗口里面的最大值。
【思路】
滑动窗口很像一个队列。
单调队列。维护队列里面的元素单调递增或单调递减。
因此我们采用Deque
进行处理:著需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
1.pop(value)
: 如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作。
2.push(value)
:如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到 (while)push元素的数值小于等于队列入口元素的数值为止。
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
pop()
push()
getMaxValue()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 class Solution { public int [] maxSlidingWindow(int [] nums, int k) { if (nums.length == 1 )return nums; int len = nums.length-k+1 ; int [] res = new int [len]; int num = 0 ; MyDeque deque = new MyDeque (); for (int i = 0 ;i<k;i++){ deque.add(nums[i]); } res[num++] = deque.peek(); for (int i = k;i<nums.length;i++){ deque.poll(nums[i-k]); deque.add(nums[i]); res[num++] = deque.peek(); } return res; } } class MyDeque { Deque<Integer> deque = new LinkedList <>(); void poll (int val) { if (!deque.isEmpty() && val == deque.peek()){ deque.poll(); } } void add (int val) { while (!deque.isEmpty() && val > deque.getLast()){ deque.removeLast(); } deque.add(val); } int peek () { return deque.peek(); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 type MyQueue struct { queue []int } func NewMyQueue () *MyQueue{ return &MyQueue{ queue: make ([]int ,0 ), } } func (m *MyQueue) Empty()bool { return len (m.queue) == 0 } func (m *MyQueue) Front()int { return m.queue[0 ] } func (m *MyQueue) Back()int { return m.queue[len (m.queue)-1 ] } func (m *MyQueue) Push(val int ){ for !m.Empty() && val > m.Back(){ m.queue = m.queue[:len (m.queue)-1 ] } m.queue = append (m.queue,val) } func (m *MyQueue) Pop(val int ){ if !m.Empty() && val == m.Front(){ m.queue = m.queue[1 :] } } func maxSlidingWindow (nums []int , k int ) []int { queue := NewMyQueue() length := len (nums) res := make ([]int ,0 ) for i := 0 ; i < k ; i++{ queue.Push(nums[i]) } res = append (res,queue.Front()) for i := k ; i < length;i++{ queue.Pop(nums[i-k]) queue.Push(nums[i]) res = append (res,queue.Front()) } return res }
347.前k个高频元素 文章:8. 前K个高频元素
题目:347. 前 K 个高频元素
视频:优先级队列正式登场!大顶堆、小顶堆该怎么用?| LeetCode:347.前 K 个高频元素_哔哩哔哩_bilibili
给定一个数组,求出这个数组中的每一个元素出现的频率,然后对频率找前k个高频的元素,放到数组中返回。
【思路】
1.如何求每个元素的频率 ==>使用map进行统计
2.如何对每个元素的频率进行排序 ==>使用一种容器适配器:优先级队列 (披着队列外衣的堆)
3.求前k个高频的元素
堆是一颗完全二叉树,数中每个结点的值都不小于(或不大于)其左右孩子的值。
大顶堆、小顶堆 :擅长在一个很大的数据集里面求前k个高频的或低频的结果的操作
底层实现是二叉树。父节点永远比左右节点要大,为大顶堆;比左右节点小即为小顶堆。
用堆维持k个元素。堆里面遍历完map里面所有的元素之后,对于里面的k个元素,就相等于维护了我们想要求的前k个高频元素。
使用小顶堆,因为要统计最大前k个元素,只有小顶堆每次将最小的元素弹出,最后小顶堆里积累的才是前k个最大元素。
感觉是api的胜利(bushi)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 class Solution { public int [] topKFrequent(int [] nums, int k) { 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()){ if (pq.size() < k){ pq.add(new int []{entry.getKey(),entry.getValue()}); }else { if (entry.getValue() > pq.peek()[1 ]){ pq.poll(); pq.add(new int []{entry.getKey(),entry.getValue()}); } } } int [] ans = new int [k]; for (int i = k-1 ; i>= 0 ;i--){ ans[i] = pq.poll()[0 ]; } return ans; } }
老实说,没看懂答案
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 func topKFrequent (nums []int , k int ) []int { map_num := map [int ]int {} for _,item :=range nums{ map_num[item]++ } h := &IHeap{} heap.Init(h) for key,value := range map_num{ heap.Push(h,[2 ]int {key,value}) if h.Len()>k{ heap.Pop(h) } } res := make ([]int ,k) for i:=0 ;i<k;i++{ res[k-i-1 ] = heap.Pop(h).([2 ]int )[0 ] } return res } type IHeap[][2 ]int func (h IHeap) Len()int { return len (h) } func (h IHeap) Less(i,j int ) bool { return h[i][1 ] < h[j][1 ] } func (h IHeap) Swap(i,j int ){ h[i],h[j] = h[j],h[i] } func (h *IHeap) Push(x interface {}){ *h = append (*h,x.([2 ]int )) } func (h *IHeap) Pop()interface {}{ old:= *h n := len (old) x := old[n-1 ] *h = old[0 :n-1 ] return x }
方法二:排序
1 2 3 4 5 6 7 8 9 10 11 12 13 14 func topKFrequent (nums []int , k int ) []int { ans :=[]int {} map_num := map [int ]int {} for _,item := range nums{ map_num[item]++ } for key,_ := range map_num{ ans = append (ans,key) } sort.Slice(ans,func (a,b int ) bool { return map_num[ans[a]]>map_num[ans[b]] }) return ans[:k] }
栈与队列总结
可以出一道面试题:栈里面的元素在内存中是连续分布的么?
这个问题有两个陷阱:
陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布。
陷阱2:缺省情况下,默认底层容器是deque,那么deque的在内存中的数据分布是什么样的呢?
答案是:不连续的,下文也会提到deque。
了解了栈与队列基础之后,那么可以用栈与队列:栈实现队列 (opens new window) 和 栈与队列:队列实现栈 (opens new window) 来练习一下栈与队列的基本操作。
一个队列在模拟栈弹出元素的时候只要将队列头部的元素(除了最后一个元素外)重新添加到队列尾部,此时再去弹出元素就是栈的顺序了
栈经典题目 栈在系统中的应用 如果还记得编译原理的话,编译器在 词法分析的过程中处理括号、花括号 等这个符号的逻辑,就是使用了栈这种数据结构。
再举个例子,linux系统中,cd这个进入目录的命令我们应该再熟悉不过了。
这个命令最后进入a目录,系统是如何知道进入了a目录呢 ,这就是栈的应用。这在leetcode上也是一道题目,编号:71. 简化路径,大家有空可以做一下。
递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中 ,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
括号匹配问题 使用栈解决的经典问题 。
思路:需要分析三种情况(多余、类型不匹配、顺序不对)
一个小技巧:在匹配左括号的时候,右括号先入栈,只需要匹配当前元素和栈顶元素是否相等即可。
字符串去重问题 思路:把字符串顺序放到一个栈中,如果相同的话栈就弹出,这样最后栈里剩下的元素就是相邻不相同的元素了。
逆波兰表达式 思路:当遇到四则运算符号时,就对栈内的元素进行四则运算后压入栈,最后返回栈顶元素即可。
需要注意减号和除号前后元素的运算顺序
队列的经典题目 滑动窗口最大值问题 思路:单调队列 。我们只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值由大到小。
需要注意单调队列的写法不唯一,不同场景写法不同。
求前k个高频元素 思路:使用优先级队列 对部分频率进行排序(大顶堆和小顶堆)。
总结 队列的题目还没有完全理解,复盘的时候需要着重复习!
在此感谢卡尔劳斯的知识分享!:)