内容:
- 逆波兰表达式(150)
- 滑动窗口最大值(239)
- 前K个高频元素(347)
- 栈与队列总结
1.逆波兰表达式求值
难度:🔥🔥
1.1 思路分析
我们需要知道这道题是如何使用栈结构进行计算的,其实逆波兰表达式相当于二叉树的后序遍历,我们遇到数据就将该数据加入到栈中,遇到运算符就从栈顶弹出两个数据运算再加入到栈中,直到遍历到最后就得到了我们想要的值。
由于这题所给的数据都是合法的,我们就不需要去处理异常情况了。
如图:
1.2 代码实现
class Solution {
//题目所给数据都是合法的
public int evalRPN(String[] tokens) {
Deque<Integer> deque = new ArrayDeque<>();
for(String s : tokens){
if ("+".equals(s)){
deque.push(deque.pop() + deque.pop());
}else if("-".equals(s)){//先进栈的数据减去后进栈的数据
deque.push(-deque.pop() + deque.pop());
}else if("*".equals(s)){
deque.push(deque.pop() * deque.pop());
}else if("/".equals(s)){
int nums1 = deque.pop();
int nums2 = deque.pop();
deque.push(nums2 / nums1);
}else {
deque.push(Integer.valueOf(s));
}
}
return deque.pop();
}
}
1.3 收获总结
- 后缀表达式对于计算机来说是十分友好的,而我们人更习惯于中缀表达式
2.滑动窗口最大值
难度:🔥🔥🔥🔥🔥🔥
1.1 思路分析
本题较难,本次刷题先跳过
1.2 代码实现
//解法一
//自定义数组
class MyQueue {
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val) {
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val) {
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
//队列队顶元素始终为最大值
int peek() {
return deque.peek();
}
}
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;
//自定义队列
MyQueue myQueue = new MyQueue();
//先将前k的元素放入队列
for (int i = 0; i < k; i++) {
myQueue.add(nums[i]);
}
res[num++] = myQueue.peek();
for (int i = k; i < nums.length; i++) {
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
myQueue.poll(nums[i - k]);
//滑动窗口加入最后面的元素
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
}
//解法二
//利用双端队列手动实现单调队列
/**
* 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
* 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
*/
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
ArrayDeque<Integer> deque = new ArrayDeque<>();
int n = nums.length;
int[] res = new int[n - k + 1];
int idx = 0;
for(int i = 0; i < n; i++) {
// 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
// 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
while(!deque.isEmpty() && deque.peek() < i - k + 1){
deque.poll();
}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
if(i >= k - 1){
res[idx++] = nums[deque.peek()];
}
}
return res;
}
}
1.3 收获总结
3.前K个高频元素
难度:🔥🔥🔥🔥
1.1 思路分析
这道题目就引出了优先队列的使用
存储数组中的值以及对应值出现的次数是Map的拿手好戏,接下来我们可以对Map的Value进行排序,即可得到前K个高频元素。
那我们可不可以只对前K个元素进行排序呢?答案是可以的。堆这种数据结构就可以实现,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。而大顶堆虽然看似符合我们要找前K个高频元素的要求,但如果只对K个元素进行排序,那么大顶堆就会优先poll()出数值大的元素,剩下的是低频元素,所以我们使用小顶堆。
但队列中的元素小于 K 个时,我们直接添加。反之我们判断堆顶元素与要添加值的大小,再判断是否添加。
如图:
1.2 代码实现
基于小顶堆实现
class Solution {
public int[] topKFrequent(int[] nums, int k) {
//使用Map和小顶堆来解决这个问题
Map<Integer,Integer> map = new HashMap<>();
//将我们的键值对放入map中
for(int n : nums){
//Map.getOrDefault(key,默认值);
//Map中会存储一一对应的key和value。
//如果在Map中存在key,则返回key所对应的的value。
//如果在Map中不存在key,则返回默认值。
map.put(n,map.getOrDefault(n,0) + 1);
}
//按照小顶堆排序,小的value放在上面
PriorityQueue<int[]> queue = new PriorityQueue<>((pair1,pair2) -> pair1[1] - pair2[1]);
//遍历map,添加到优先队列中,entrySet()可以直接获取对应的 Key 和 Value
for(Map.Entry<Integer,Integer> entry : map.entrySet()){
//如果队列中的元素小于k个,我们直接添加
if (queue.size() < k){//以二元数组形式添加
queue.add(new int[]{entry.getKey(),entry.getValue()});
}else {//反之,如果我们要添加的Value大于堆顶的Value
if (entry.getValue() > queue.peek()[1]){
queue.poll();
queue.add(new int[]{entry.getKey(),entry.getValue()});
}
}
}
//按照高频在前,低频在后的顺序
int[] result = new int[k];
for(int i = k - 1;i >= 0;i--){
result[i] = queue.poll()[0];
}
return result;
}
}
1.3 收获总结
- 理解什么是大顶堆,小顶堆以及什么时候该使用大顶堆,小顶堆。