代码随想录Day13 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347. 前 K 个高频元素 | 链表操作

58 阅读1分钟

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

  1. 当deque不为空且第一个元素=i - k时,deque中第一个元素弹出。
  2. 维持队列的单调递减,当deque不为空,且nums[i]大于deque的尾部元素,尾部元素弹出。
  3. i进入队列。
  4. 当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的使用。