当前位置:   article > 正文

算法训练day13-栈与队列2_priorityqueue pq = new priorityqueue<>((pai

priorityqueue pq = new priorityqueue<>((pair1,pair2)->pair1[1]-pair2[

150. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

知识科普

逆波兰表达式:是一种后缀表达式,所谓后缀就是指算符写在后面。

平常使用的算式则是一种中缀表达式,如 ( 1 + 2 ) * ( 3 + 4 ) 。

该算式的逆波兰表达式写法为 ( ( 1 2 + ) ( 3 4 + ) * ) 。

逆波兰表达式主要有以下两个优点:

  • 去掉括号后表达式无歧义,上式即便写成 1 2 + 3 4 + * 也可以依据次序计算出正确结果。

  • 适合用栈操作运算:遇到数字则入栈;遇到算符则取出栈顶两个数字进行计算,并将结果压入栈中

思路:

        * 发现规律:逆波兰表达式是一种数值在前,算符在后的算式表达。

        * 最先参与运算的两个数一定是在算符之前的紧挨着的两个数。

        * 事实上每次参与运算的仅有两个数,而他们的算符就在后面。

        * 于是刚好可以利用栈来实现:

        * 定义一个栈 stack 

        * 从左到右遍历表达式字符,当遇到一个数字时就入栈,当遇到一个运算符时,弹出两个元                      素进行计算,并将计算后的结果压入栈

        * 重复以上,当全部遍历完时,栈中的数字就是最终的结果。

代码实现

  1. class Solution {
  2. public int evalRPN(String[] tokens) {
  3. Stack<String> stack = new Stack<>();
  4. for(int i=0;i<tokens.length;i++){
  5. if(!tokens[i].equals("+")&&!tokens[i].equals("-")&&!tokens[i].equals("*")&&!tokens[i].equals("/")){
  6. stack.push(tokens[i]);
  7. }else{
  8. int v1 = Integer.parseInt(stack.pop());
  9. int v2 = Integer.parseInt(stack.pop());
  10. int res = 0;
  11. if(tokens[i].equals("+")){
  12. res = v2 + v1;
  13. }
  14. if(tokens[i].equals("-")){
  15. res = v2 - v1;
  16. }
  17. if(tokens[i].equals("*")){
  18. res = v2 * v1;
  19. }
  20. if(tokens[i].equals("/")){
  21. res = v2 / v1;
  22. }
  23. stack.push(res+"");
  24. }
  25. }
  26. return Integer.parseInt(stack.pop());
  27. }
  28. }
  1. class Solution {
  2. public int evalRPN(String[] tokens) {
  3. Deque<Integer> stack = new LinkedList();
  4. for (String s : tokens) {
  5. if ("+".equals(s)) { // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
  6. stack.push(stack.pop() + stack.pop()); // 注意 - 和/ 需要特殊处理
  7. } else if ("-".equals(s)) {
  8. stack.push(-stack.pop() + stack.pop());
  9. } else if ("*".equals(s)) {
  10. stack.push(stack.pop() * stack.pop());
  11. } else if ("/".equals(s)) {
  12. int temp1 = stack.pop();
  13. int temp2 = stack.pop();
  14. stack.push(temp2 / temp1);
  15. } else {
  16. stack.push(Integer.valueOf(s));
  17. }
  18. }
  19. return stack.pop();
  20. }
  21. }

239. 滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回滑动窗口中的最大值。

进阶:

你能在线性时间复杂度内解决此题吗?

思路:

设计一个单调队列,保证队列中的元素从大到小排列,窗口的出口元素就是最大值。

设计单调队列的时候,pop,和push操作要保持如下规则:

  1. pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
  2. push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止

代码实现

一.直接使用双端队列实现单调队列的功能

  1. //利用双端队列手动实现单调队列
  2. /**
  3. * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
  4. * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
  5. */
  6. class Solution {
  7. public int[] maxSlidingWindow(int[] nums, int k) {
  8. ArrayDeque<Integer> deque = new ArrayDeque<>();
  9. int n = nums.length;
  10. int[] res = new int[n - k + 1];
  11. int idx = 0;
  12. for(int i = 0; i < n; i++) {
  13. // 根据题意,i为nums下标,是要在[i - k + 1, i] 中选到最大值,只需要保证两点
  14. // 1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
  15. while(!deque.isEmpty() && deque.peek() < i - k + 1){
  16. deque.poll();
  17. }
  18. // 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
  19. while(!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
  20. deque.pollLast();
  21. }
  22. deque.offer(i);
  23. // 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
  24. if(i >= k - 1){
  25. res[idx++] = nums[deque.peek()];
  26. }
  27. }
  28. return res;
  29. }
  30. }

二.自定义一个单调队列类,然后在程序中使用

  1. class MyQueue {
  2. Deque<Integer> deque = new LinkedList<>();
  3. //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
  4. //同时判断队列当前是否为空
  5. void poll(int val) {
  6. if (!deque.isEmpty() && val == deque.peek()) {
  7. deque.poll();
  8. }
  9. }
  10. //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
  11. //保证队列元素单调递减
  12. //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
  13. void add(int val) {
  14. while (!deque.isEmpty() && val > deque.getLast()) {
  15. deque.removeLast();
  16. }
  17. deque.add(val);
  18. }
  19. //队列队顶元素始终为最大值
  20. int peek() {
  21. return deque.peek();
  22. }
  23. }
  24. class Solution {
  25. public int[] maxSlidingWindow(int[] nums, int k) {
  26. if (nums.length == 1) {
  27. return nums;
  28. }
  29. int len = nums.length - k + 1;
  30. //存放结果元素的数组
  31. int[] res = new int[len];
  32. int num = 0;
  33. //自定义队列
  34. MyQueue myQueue = new MyQueue();
  35. //先将前k的元素放入队列
  36. for (int i = 0; i < k; i++) {
  37. myQueue.add(nums[i]);
  38. }
  39. res[num++] = myQueue.peek();
  40. for (int i = k; i < nums.length; i++) {
  41. //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
  42. myQueue.poll(nums[i - k]);
  43. //滑动窗口加入最后面的元素
  44. myQueue.add(nums[i]);
  45. //记录对应的最大值
  46. res[num++] = myQueue.peek();
  47. }
  48. return res;
  49. }
  50. }

347.前 K 个高频元素

给定一个非空的整数数组,返回其中出现频率前 k 高的元素。

示例 1:

  • 输入: nums = [1,1,1,2,2,3], k = 2
  • 输出: [1,2]

示例 2:

  • 输入: nums = [1], k = 1
  • 输出: [1]

思路

1.用map统计元素出现的次数

2.遍历map,将map中的元素二元组,按照出现频率顺序存储在一个优先级队列中(即一个堆中)

3.不断更新堆,最后留下k个出现频率最高的节点,依次弹出并返回。

代码实现

1.小顶堆

  1. class Solution {
  2. public int[] topKFrequent(int[] nums, int k) {
  3. // 1.用map统计数组元素出现的频率
  4. // 2.用优先级队列对频率排序
  5. // 3.输出按照频率排序的元素
  6. // 定义统计元素出现次数的map
  7. Map<Integer,Integer> map = new HashMap<>();
  8. for(int n:nums){
  9. map.put(n,map.getOrDefault(n,0)+1);
  10. }
  11. // 定义小顶堆
  12. //在优先队列中存储二元组(n,cnt),cnt表示元素值n在数组中的出现次数
  13. //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
  14. PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
  15. for(Map.Entry<Integer,Integer> entry:map.entrySet()){
  16. if(pq.size() < k){
  17. pq.add(new int[]{entry.getKey(),entry.getValue()});
  18. }else{
  19. if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
  20. pq.poll();//弹出队头(小顶堆的根结点),即把堆里出现次数最少的那个删除,留下的就是出现次数多的了
  21. pq.add(new int[]{entry.getKey(),entry.getValue()});
  22. }
  23. }
  24. }
  25. int[] res = new int[k];
  26. // 将小顶堆元素依次弹出
  27. // 最后的输出的元素顺序按照出现频率从小到大排列
  28. for(int i=0;i<k;i++){
  29. res[i] = pq.poll()[0];
  30. }
  31. return res;
  32. }
  33. }

2.大顶堆

  1. /*Comparator接口说明:
  2. * 返回负数,形参中第一个参数排在前面;返回正数,形参中第二个参数排在前面
  3. * 对于队列:排在前面意味着往队头靠
  4. * 对于堆(使用PriorityQueue实现):从队头到队尾按从小到大排就是最小堆(小顶堆),
  5. * 从队头到队尾按从大到小排就是最大堆(大顶堆)--->队头元素相当于堆的根节点
  6. * */
  7. class Solution {
  8. //解法1:基于大顶堆实现
  9. public int[] topKFrequent1(int[] nums, int k) {
  10. Map<Integer,Integer> map = new HashMap<>();//key为数组元素值,val为对应出现次数
  11. for(int num:nums){
  12. map.put(num,map.getOrDefault(num,0)+1);
  13. }
  14. //在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
  15. //出现次数按从队头到队尾的顺序是从大到小排,出现次数最多的在队头(相当于大顶堆)
  16. PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2)->pair2[1]-pair1[1]);
  17. for(Map.Entry<Integer,Integer> entry:map.entrySet()){//大顶堆需要对所有元素进行排序
  18. pq.add(new int[]{entry.getKey(),entry.getValue()});
  19. }
  20. int[] ans = new int[k];
  21. for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
  22. ans[i] = pq.poll()[0];
  23. }
  24. return ans;
  25. }

                  

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/繁依Fanyi0/article/detail/277028
推荐阅读
相关标签
  

闽ICP备14008679号