150. 逆波兰表达式求值
题目链接:150. 逆波兰表达式求值
思路: 使用栈来模拟,当碰到运算符时,从栈顶一次弹出两个数字做运算,将结果压入栈中,栈中剩下的最后一个元素为答案。
class Solution {
public int evalRPN(String[] tokens) {
Stack<Integer> stack = new Stack<>();
for (String token : tokens) {
if ("+-*/".contains(token)) {
int a = stack.pop(), b = stack.pop();
switch(token) {
case "+":
stack.push(a + b);
break;
case "-":
stack.push(b - a);
break;
case "*":
stack.push(a * b);
break;
case "/":
stack.push(b / a);
break;
}
} else {
stack.push(Integer.parseInt(token));
}
}
return stack.pop();
}
}
总结:
和相邻两个元素相关的问题可以使用栈来解决
239. 滑动窗口最大值
题目链接:239. 滑动窗口最大值
解法一:
使用一个队列充当不断滑动的窗口,每次滑动记录其中的最大值。如何在 O(1) 时间计算最大值,只需要一个特殊的数据结构「单调队列」,push 方法依然在队尾添加元素,但是要把前面比自己小的元素都删掉,直到遇到更大的元素才停止删除。
解法二:
使用一个双端队列来存储 nums 的索引 i
- 当deque不为空且第一个元素=i - k时,deque中第一个元素弹出。
- 维持队列的单调递减,当deque不为空,且nums[i]大于deque的尾部元素,尾部元素弹出。
- i进入队列。
- 当i的值大于k - 1时,数组存入原数组中索引为 i 的数。
// 解法一
// 定义一个单调队列
class Solution {
class MonotonicQueue {
Deque<Integer> q = new LinkedList<>();
public void push(int n) {
while (!q.isEmpty() && q.getLast() < n) {
q.pollLast();
}
q.addLast(n);
}
public void pop(int n) {
if (!q.isEmpty() && n == q.getFirst()) {
q.pollFirst();
}
}
public int max() {
return q.getFirst();
}
}
public int[] maxSlidingWindow(int[] nums, int k) {
MonotonicQueue queue = new MonotonicQueue();
int[] res = new int[nums.length - k + 1];
int index = 0;
for (int i = 0; i < nums.length; i++) {
if (i < k - 1) {
queue.push(nums[i]);
} else {
queue.push(nums[i]);
res[index++] = queue.max();
queue.pop(nums[i - k + 1]);
}
}
return res;
}
}
// 解法二
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
Deque<Integer> queue = new LinkedList<>();
int[] res = new int[nums.length - k + 1];
int index = 0;
for (int i = 0; i < nums.length; i++) {
// 1.处理头部超期元素
if (!queue.isEmpty() && queue.peek() == i - k) {
queue.removeFirst();
}
// 2.保持单调递减队列
while (!queue.isEmpty() && nums[i] >= nums[queue.peekLast()]) {
queue.removeLast();
}
// 3.新元素入队
queue.offer(i);
// 4.数组添加元素
if (i >= k - 1) {
res[index++] = nums[queue.peek()];
}
}
return res;
}
}
总结:
347. 前 K 个高频元素
题目链接:347. 前 K 个高频元素
思路:
首先用map来存储每个数字(key)和出现次数(value),然后将键值对存入一个大小为k的小顶堆中,如果进入的元素大于小顶堆的根结点,那么根结点弹出,这样最后剩下的元素即为出现频率最高的几个元素。
我的代码:
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<>((a, b) -> a[1] - b[1]);
// 小顶堆只需要维持k个元素有序
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
// 小顶堆元素个数小于k个时直接加
if (pq.size() < k) {
pq.add(new int[]{entry.getKey(), entry.getValue()});
} else {
// 当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
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;
}
}
总结:
学习了小顶堆和entrySet的使用。