目录
题目链接:150. 逆波兰表达式求值 - 力扣(LeetCode)
题目链接:239. 滑动窗口最大值 - 力扣(LeetCode)
题目链接:347. 前 K 个高频元素 - 力扣(LeetCode)
Your name, is the shortest love poem l've ever seen.
使用栈解决逆波兰表达式求值问题,思路:
- 使用一个栈来保存操作数。
- 遍历给定的
tokens
数组:- 如果当前的 token 是一个操作数,则将其压入栈中。
- 如果当前的 token 是一个运算符,则从栈中弹出两个操作数,进行相应的计算,并将结果压入栈中。
- 最终栈中只会剩下一个元素,即表达式的计算结果。
class Solution150 {
public int evalRPN(String[] tokens) {
Deque<Integer> stack = new LinkedList<>(); // 使用Deque接口创建栈对象
for (String token : tokens) {
if (isOperator(token)){
int b = stack.pop();
int a = stack.pop();
stack.push(applyOperator(a, b, token)); // 遇操作符计算后再入栈
}else {
stack.push(Integer.parseInt(token)); // 整数入栈
}
}
return stack.pop();
}
private boolean isOperator(String token){
return "+-*/".contains(token);
}
private int applyOperator(int a, int b, String operator) {
switch (operator){
case "+":
return a + b;
case "-":
return a - b;
case "*":
return a * b;
case "/":
return a / b;
default:
throw new IllegalArgumentException("Invalid operator: " + operator);
}
}
}
利用双端队列手动实现单调队列:
要解决滑动窗口中的最大值问题,可以使用一个双端队列 Deque,这种方法可以在 O(n) 时间复杂度内完成。双端队列的主要作用是在每个窗口位置维护可能成为窗口最大值的元素的索引,确保队列的前端始终是当前窗口的最大值。思路:
创建一个 Deque
用来存储数组 nums
中元素的索引,并保证 Deque
中的索引对应的 nums
值总是从大到小排序。
- 移除队列前端不在窗口内的索引;
- 移除队列末端所有小于当前元素
nums[i]
的元素索引,因为如果当前元素比它们大,它们就不可能是任何将来窗口的最大值了; - 将当前索引
i
添加到队列末端; - 更新结果数组:从窗口第一个完整窗口开始,队列的前端就是每个窗口的最大值;
遍历完成后,result
数组包含了每个滑动窗口的最大值。
class Solution239 {
public int[] maxSlidingWindow(int[] nums, int k) {
int n = nums.length;
int[] result = new int[n - k + 1];
int ri = 0; // 存放数组的索引
Deque<Integer> deque = new ArrayDeque<>(); // 用于存放索引的双端队列
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){
result[ri++] = nums[deque.peek()];
}
}
return result;
}
}
哈希表 + 优先队列(小顶堆)
要找到数组中出现频率前 k
高的元素,可以使用一个 HashMap
来统计每个元素的频率,然后使用一个优先队列(Java
中优先队列实现类为PriorityQueue
)来存储频率最高的 k
个元素。
- 使用一个
HashMap
来记录每个元素出现的频率。键是元素,值是该元素的频率。 - 使用一个优先队列(
PriorityQueue
)来保存频率最高的k
个元素。这里使用最小堆,这样当堆的大小超过k
时,我们可以移除频率最小的元素,始终保持堆的大小为k;
- 最后,从堆中提取
k
个元素,这些元素就是频率最高的k
个元素。
class Solution347 {
public int[] topKFrequent(int[] nums, int k) {
// 统计每个元素的频率
Map<Integer, Integer> frequencyMap = new HashMap<>();
for (int num : nums) {
frequencyMap.put(num, frequencyMap.getOrDefault(num, 0) + 1);
}
// 使用优先队列(最小堆)来保存频率最高的k个元素
PriorityQueue<Integer> heap = new PriorityQueue<>(
// new Comparator<Integer>() {
// @Override
// public int compare(Integer o1, Integer o2) {
// return frequencyMap.get(o1) - frequencyMap.get(o2);
// }
// }
// 简写:
(o1, o2) -> frequencyMap.get(o1) - frequencyMap.get(o2)
);
// 遍历频率表,维护一个大小为k的堆
for (int num : frequencyMap.keySet()) {
heap.add(num);
if (heap.size() > k){
heap.poll(); // 如果堆的大小超过 k,移除堆顶元素,即频率最小的元素
}
}
// 提取堆中的元素
int[] result = new int[k];
for (int i = 0; i < k; i++) {
result[i] = heap.poll();
}
return result;
}
}
记录一点:
(o1, o2) -> frequencyMap.get(o1) - frequencyMap.get(o2)
这是JDK8 Lambda表达式的写法。用于定义两个元素之间的比较逻辑。在这个具体的例子中,Lambda 表达式作为一个比较器(Comparator)传递给 PriorityQueue
的构造函数。
- 时间复杂度: O(N log k)
- 空间复杂度: O(N)