Day 11栈与队列part 02
150. 逆波兰表达式求值
想到的问题:如何判断这个字符是符号还是数字
可以直接用equals判断.
==号:
-
如果是基本数据类型比较的是值
-
引用数据类型比较的是地址值是否相等
equals:
-
比较的是地址值
-
提供方法重写自定义,可以比较引用数据类型的内容
class Person {
String name;
int age;
@Override
public boolean equals(Object obj) {
if (this == obj) return true; // 如果是同一个对象
if (obj == null || getClass() != obj.getClass()) return false; // 如果对象为空或类型不同
Person person = (Person) obj; // 类型转换
return age == person.age && Objects.equals(name, person.name); // 比较内容
}
}
题目给出的数据都是合法的
存字符串
private static int evalRPN(String[] tokens) {
Stack<String> stack = new Stack<>();
for (String token : tokens) {
if (token.equals("*")) {
Integer b = Integer.valueOf(stack.pop());
Integer a = Integer.valueOf(stack.pop());
String result = String.valueOf(a * b);
stack.push(result);
} else if (token.equals("/")) {
Integer b = Integer.valueOf(stack.pop());
Integer a = Integer.valueOf(stack.pop());
String result = String.valueOf(a / b);
stack.push(result);
} else if (token.equals("+")) {
Integer b = Integer.valueOf(stack.pop());
Integer a = Integer.valueOf(stack.pop());
String result = String.valueOf(a + b);
stack.push(result);
} else if (token.equals("-")) {
Integer b = Integer.valueOf(stack.pop());
Integer a = Integer.valueOf(stack.pop());
String result = String.valueOf(a - b);
stack.push(result);
} else {
stack.push(token);
}
}
return Integer.parseInt(stack.pop());//valueOf
}
存数值
除了除号以外都是能够,直接操作
private static int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if (token.equals("*")) {
// Integer b = stack.pop();
// Integer a = stack.pop();
// stack.push(a * b);
stack.push(stack.pop() * stack.pop());
} else if (token.equals("/")) {
Integer b = stack.pop();
Integer a = stack.pop();
stack.push(a / b);
} else if (token.equals("+")) {
// Integer b = stack.pop();
// Integer a = stack.pop();
// stack.push(a + b);
stack.push(stack.pop() + stack.pop());
} else if (token.equals("-")) {
// Integer b = stack.pop();
// Integer a = stack.pop();
// stack.push(a - b);
stack.push(-stack.pop() + stack.pop());
} else {
stack.push(Integer.valueOf(token));
}
}
return stack.pop();
}
parseInt和valueOf的区别
parseInt:
- 不会自动装箱为Integer,直接解析为int
valueOf:
- 会先变为Integer,如果要返回的是int,则会再次拆箱。
两者 不合法会报异常
239. 滑动窗口最大值
用双端队列来自定义单调队列 思路 我们实现自定义方法
- 遍历到元素,若此元素等于 队列的出口,就弹出出口处,
- 若此元素,队列不为空,大于队列入口的元素,就while,将小的元素,从入口处弹出
- 返回的是队列出口的元素,维护队列出口的元素,为最大值即可
注意开头判断,nums的长度,确定result数组的长度
一开始我们先装入k个,拿一次最大值。之后再for循环,自动操作
弹出的时候,只判断一次。根据nums[num-k]和队首
//!deque.isEmpty()非空判断是必须的,加入的值,比队尾的元素大就要弹出
deque和queue,前者为双端队列,两端都可以插入和弹出
错误点:
加入元素,val与队尾元素的判断
条件类型 | 行为 | 适用场景 | 风险 |
---|---|---|---|
val > (正确) | 保留所有可能成为最大值的重复元素 | 通用场景,保证窗口完整性 | 无 |
val >= (错误) | 提前移除重复的候选最大值 | 无 | 窗口滑动时可能丢失最大值,导致错误结果 |
获取最大元素,是否弹出的问题
操作 | 目的 | 关键影响 |
---|---|---|
myQueue.peek() | 安全获取当前窗口最大值 | 不破坏队列结构 |
myQueue.poll(int val) | 清理过期元素 | 仅在必要时移除队首 |
代码(含详细注释)
public class Sliding_Window_Maximum {
static class MyQueue { //类不是方法,不加括号
Deque<Integer> deque = new LinkedList<>();
//弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
//同时判断队列当前是否为空
void poll(int val) {
//是if不是while
if (!deque.isEmpty() && val == deque.peek()) {
deque.poll();//deque.pollFirst();
}
}
//添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
//保证队列元素单调递减
//比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
void add(int val) {
//注意是前k个元素
//!deque.isEmpty()非空判断是必须的,加入的值,比队尾的元素大就要弹出
//大于还是大于等于,区别在何处,加上个等于会出错
//保留所有可能成为最大值的重复元素 ||val >= (错误) 提前移除重复的候选最大值
while (!deque.isEmpty() && val > deque.getLast()) {
deque.removeLast();
}
deque.add(val);
}
//队列队顶元素始终为最大值
//记录先不弹出,若弹出会出问题,因为我们循环开始要做判断,弹出会影响之后循环的判断
//获取最大值不改变队列结构,有的这个最大值,后面几次同样也是最大值,提前弹出影响后面
int peek() {
return deque.peek();//deque.peekFirst()
}
}
private int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;//只有一个元素,最大值就是它本身
}
//k的取值,最大不超过 nums.length 2-2+1 k最大值保底
int len = nums.length - k + 1;//最大值数len至少要为一,只要nums>=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]); // 本人错误一开始把num用在此处
}
res[num++] = myQueue.peek();//本k个元素最大数
for (int i = k; i < nums.length; i++) {
//滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
//有进有出
myQueue.poll(nums[i - k]);
//滑动窗口加入最后面的元素 窗口右移
myQueue.add(nums[i]);
//记录对应的最大值
res[num++] = myQueue.peek();
}
return res;
}
}
347. 前 K 个高频元素
维护k个元素,如果采用大顶堆,当我们插入一个元素,就会弹出一个,这样只能维护前k小的元素,而不是最大的。
我们PriorityQueue 存入的是pair类,存着的是元素和出现的频率,我们排序是排序频率,所以我们这里采用的是PriorityQueue 里面的自定义排序
如果有限级队列的大小大于k,我们就弹出(插入的时候我们就已经排过序了,小的在最顶,弹出的为小的)
我们这里可以使用数组,结构会简单一下
大根堆和小根堆的实现区别在于自定义排序规则
pair2[1] - pair1[1] ,pair2[1]比pair1[1] 大,就pair2[1]排前面 ,降序 大->小
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);//大
PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);//小
注意:这里的pair名字是自己给的,出门在外,名号都是自己给的
(pair1[1], pair2[1])->pair2[1] - pair1[1])这个是错误的 我们只用写数组名称就可以
传入int[]数组作为对象, 插入 居然可以用new int[]{} 直接在花括号中填入构造数组的数据,我们取出元素的时候,也可以用[] 来指定我们要取出的位置
PriorityQueue<int[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
pairs.add(new int[]{it.getKey(), it.getValue()});
ans[i] = pairs.poll()[0];
基于小顶堆实现
-
我们先用map对数据及频率进行排序。
-
有限级队列 存放的是对象,自定义排序,按照对象(数组)的频率进行排序
-
未满k个就插入||满了就对比频率(堆顶,频率是优先级队列中最小的),大于堆顶就堆顶弹出,数据插入,
-
我们最后需要返回,频率从大到小填到数组中,有技巧得用for循环
//基于小顶堆实现
private static int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> map = new HashMap<>();//多态
for (int num : nums) {
map.put(num, map.getOrDefault(num, 0) + 1);
}
//自定义排序
//在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
PriorityQueue<int[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
for (Map.Entry<Integer, Integer> it : map.entrySet()) {
//没满,就填充
if (pairs.size() < k) {//小顶堆元素个数小于k个时直接加
pairs.add(new int[]{it.getKey(), it.getValue()});
} else { //满了就选择性加入
if (it.getValue() > pairs.peek()[1]) {//注意这里是频率排序,也就比较第二位的value
pairs.poll(); //弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
pairs.add(new int[]{it.getKey(), it.getValue()});
}
}
}
int[] ans = new int[k];
//要保持频率从大到小收集
//依次弹出小顶堆,先弹出的是堆的根,出现次数少,后面弹出的出现次数多
for (int i = k - 1; i >= 0; i--) {
ans[i] = pairs.poll()[0];
}
return ans;
}
基于大顶堆实现
不管填入多少,我们在最后for循环只取前k个
//基于大顶堆实现
private static 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[]> pairs = new PriorityQueue<>((pair1, pair2) -> pair2[1] - pair1[1]);
for (Map.Entry<Integer, Integer> it : map.entrySet())a {
//不管填入多少,我们在最后只取前k个
pairs.add(new int[]{it.getKey(), it.getValue()});
}
int[] ans = new int[k];
for (int i = 0; i < k; i++) {
ans[i] = pairs.poll()[0];
}
return ans;
}
定义泛型Pair类
1. 基础结构设计
public class Pair<L, R> {
private final L left; // 左值(不可变)
private final R right; // 右值(不可变)
public Pair(L left, R right) {
this.left = left;
this.right = right;
}
public L getLeft() { return left; }
public R getRight() { return right; }
@Override
public String toString() {
return "Pair(" + left + ", " + right + ")";
}
}
关键点
:使用泛型<L, R>
支持任意类型组合,字段设为final确保不可变性
扩展
:若需可变性,可添加setLeft()和setRight()
方法(参考MutablePair实现)
二、堆(PriorityQueue)中的使用
2.1 自定义比较逻辑
当Pair
需要按某个字段排序时,需提供比较器(Comparator):
// 示例:按Pair的右值升序排列(小顶堆)
PriorityQueue<Pair<Integer, String>> minHeap = new PriorityQueue<>(
Comparator.comparingInt(pair -> pair.getRight())
);
// 或按左值降序排列(大顶堆)
PriorityQueue<Pair<String, Double>> maxHeap = new PriorityQueue<>(
(p1, p2) -> p2.getLeft().compareTo(p1.getLeft())
);
说明
:使用 Comparator.comparingInt()或Lambda表达式定义排序规则
2.2 完整堆操作示例
public static void main(String[] args) {
// 创建按右值(整数)排序的小顶堆
PriorityQueue<Pair<String, Integer>> pq = new PriorityQueue<>(
Comparator.comparingInt(Pair::getRight)
);
// 添加元素
pq.add(new Pair<>("TaskA", 3));
pq.add(new Pair<>("TaskB", 1));
pq.add(new Pair<>("TaskC", 2));
// 弹出元素(按优先级升序)
while (!pq.isEmpty()) {
Pair<String, Integer> task = pq.poll();
System.out.println(task.getLeft() + " (优先级:" + task.getRight() + ")");
}
}
}