代码随想录算法训练营第11天 | 150. 逆波兰表达式求值、239. 滑动窗口最大值、347.前 K 个高频元素

目录

单调队列

1. 单调队列的定义

2. 单调队列的操作

3. 单调队列的应用

4. 单调队列的优势

5. 实现细节

150.逆波兰表达式求值

1.解题思路

2.我的实现

实现遇到的问题

对于break的使用

将字符转换成int

代码

代码中的错误

3.标准实现

区别

4.题目总结

239. 滑动窗口最大值

1.解题思路

2.我的实现

实现遇到的问题

代码

3.标准实现

4.题目总结

347.前 K 个高频元素(!!!)

1.解题思路

2.我的实现

实现遇到的问题

代码

3.标准代码

4.题目总结


单调队列

单调队列(Monotonic Queue)是一种特殊的队列结构,通常用于在滑动窗口最大最小值等问题中实现高效的查询操作。以下是单调队列的基本概念及其在相关问题中的应用总结:

1. 单调队列的定义

  • 单调递增队列:队列中的元素按值递增排序。当我们向队列中添加一个新元素时,所有比它大的元素都会被移除,以保持队列的单调性。及最后一个元素最大
  • 单调递减队列:队列中的元素按值递减排序。当我们向队列中添加一个新元素时,所有比它小的元素都会被移除,以保持队列的单调性。及最后一个元素最小

2. 单调队列的操作

  • 入队 (enqueue):插入新元素时,根据单调性移除不符合条件的元素。
  • 出队 (dequeue):根据需要从队列头部移除元素,通常与滑动窗口的左边界移动有关。
  • 保持单调性:通过在入队时移除不符合单调性的元素,队列始终保持单调性。

3. 单调队列的应用

  • 滑动窗口中的最大/最小值问题:在给定数组中找到每个长度为 k 的子数组的最大或最小值。单调队列可以在 O(n) 时间内完成此操作,其中 n 是数组的长度。
  • 动态维护区间最值:单调队列能够高效地维护区间中的最大值或最小值,适用于需要频繁更新区间的场景。

4. 单调队列的优势

  • 高效性:通过维护队列的单调性,可以在常数时间内获得当前区间的最值,并且只需线性时间复杂度遍历整个数组。
  • 空间效率:队列的空间复杂度通常为 O(k),其中 k 是滑动窗口的大小。

5. 实现细节

  • 数据结构:可以使用双端队列(Deque)来实现单调队列,双端队列支持在队列两端进行高效的插入和删除操作。
  • 入队操作:插入新元素时,从队尾逐一比较并移除不符合单调性的元素,然后将新元素插入队尾。
  • 出队操作:在滑动窗口问题中,当窗口左边界向右移动时,如果当前最大或最小值已不在窗口范围内,则从队列头部移除它。

150.逆波兰表达式求值

题目链接:逆波兰表达式求值

1.解题思路

把元素放入栈中,遇到操作符后,将最后两个元素拿出来操作,最后一个作为右操作符,前一个作为左操作符,然后再把操作结果放入进去。整个后缀表达式的结果就是栈中最后一个元素。

2.我的实现

实现遇到的问题

对于break的使用

1.在switch语句中break只终止当前case的执行,跳出switch语句。

2.在循环结构中break终止整个循环。

将字符转换成int

Integer.parseInt(tokens[i]) 的作用就是将位于 tokens 数组第 i 个位置上的字符串转换为整数。

代码

public int evalRPN(String[] tokens) {
        Stack<Integer> stack = new Stack<>();
        int num1,num2;
        for (int i = 0; i < tokens.length; i++) {
            switch (tokens[i]) {
                case "+":
                    num2=stack.pop();
                    num1=stack.pop();
                    stack.push(num1+num2);
                    break;
                case "-":
                    num2=stack.pop();
                    num1=stack.pop();
                    stack.push(num1-num2);
                    break;
                case "*":
                    num2=stack.pop();
                    num1=stack.pop();
                    stack.push(num1*num2);
                    break;
                case "/":
                    num2=stack.pop();
                    num1=stack.pop();
                    stack.push(num1/num2);
                    break;
                default:
                    stack.push(Integer.parseInt(tokens[i]));//将字符转换成int类型
                    break;
            }
        }
        return stack.peek();

    }

代码中的错误

3.标准实现

class Solution {
    public int evalRPN(String[] tokens) {
        Deque<Integer> stack = new LinkedList();
        for (String s : tokens) {
            if ("+".equals(s)) {        // leetcode 内置jdk的问题,不能使用==判断字符串是否相等
                stack.push(stack.pop() + stack.pop());      // 注意 - 和/ 需要特殊处理
            } else if ("-".equals(s)) {
                stack.push(-stack.pop() + stack.pop());
            } else if ("*".equals(s)) {
                stack.push(stack.pop() * stack.pop());
            } else if ("/".equals(s)) {
                int temp1 = stack.pop();
                int temp2 = stack.pop();
                stack.push(temp2 / temp1);
            } else {
                stack.push(Integer.valueOf(s));
            }
        }
        return stack.pop();
    }
}

区别

4.题目总结

  • 定义:堆栈是一种后进先出 (LIFO) 的数据结构,适合处理逆波兰表示法表达式的计算。
  • 操作:在遇到操作数时,将其压入栈中;在遇到操作符时,从栈中弹出相应数量的操作数,进行计算后将结果再压入栈中。
  • 用途:在逆波兰表达式计算中,堆栈用于存储操作数和中间计算结果。

239. 滑动窗口最大值

题目链接:滑动窗口最大值

1.解题思路

用一个双端队列,每移动一个元素就pop最后一个,push最新的一个,然后再获得最大值。

pop保持队列出口出是最大的一个元素,当进入的比前面大时,就把前面的pop出去

小的话就可以放进来,因为我们维护的是出口处的元素。

相当于这个队列中只储存两个值一个是最大值储存到第一位,一个是最后一个为最大值,储存在第二位,相等时接着后面储存

2.我的实现

实现遇到的问题

1.ArrayDeque 和 LinkedList用哪个实现Deque更好:

ArrayDeque 是滑动窗口问题中实现双端队列的最佳选择。它能够提供高效的双端操作,确保滑动窗口在处理过程中能够快速、稳定地完成元素的插入、删除和查询。

2.注意避免重复判断,重复加入会引起错误,学会用else。

代码

public static int[] maxSlidingWindow(int[] nums, int k) {

        Deque<Integer> deque = new ArrayDeque<>();
        int[] result = new int[nums.length - k+1];
        for (int i = 0; i < nums.length; i++) {
            if (deque.isEmpty()){
                deque.addLast(nums[i]);
            }

            else if (nums[i] > deque.getFirst()) {//如果大于了最前面的数
                // 那所有数都要移走
                while (!deque.isEmpty()) {
                    deque.pollLast();
                }
                deque.addLast(nums[i]);
            }else {
                while (deque.peekLast()<nums[i]){
                    deque.pollLast();
                }
                deque.addLast(nums[i]);
                int a=nums[i];
            }
            if (i >= k - 1) {
                result[i - k + 1] = deque.getFirst();
                //以窗口最后一个数为i,第一个数为i-k+1
                //当最大数等于窗口第一个数时,意味着要移除
                if (deque.getFirst() == nums[i - k + 1]) {
                    deque.pollFirst();
                }

            }

        }
        return result;
    }

3.标准实现

//解法一
//自定义数组
class MyQueue {
    Deque<Integer> deque = new LinkedList<>();
    //弹出元素时,比较当前要弹出的数值是否等于队列出口的数值,如果相等则弹出
    //同时判断队列当前是否为空
    void poll(int val) {
        if (!deque.isEmpty() && val == deque.peek()) {
            deque.poll();
        }
    }
    //添加元素时,如果要添加的元素大于入口处的元素,就将入口元素弹出
    //保证队列元素单调递减
    //比如此时队列元素3,1,2将要入队,比1大,所以1弹出,此时队列:3,2
    void add(int val) {
        while (!deque.isEmpty() && val > deque.getLast()) {
            deque.removeLast();
        }
        deque.add(val);
    }
    //队列队顶元素始终为最大值
    int peek() {
        return deque.peek();
    }
}

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        if (nums.length == 1) {
            return nums;
        }
        int len = nums.length - k + 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]);
        }
        res[num++] = myQueue.peek();
        for (int i = k; i < nums.length; i++) {
            //滑动窗口移除最前面的元素,移除是判断该元素是否放入队列
            myQueue.poll(nums[i - k]);
            //滑动窗口加入最后面的元素
            myQueue.add(nums[i]);
            //记录对应的最大值
            res[num++] = myQueue.peek();
        }
        return res;
    }
}

//解法二
//利用双端队列手动实现单调队列
/**
 * 用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
 * 单调队列类似 (tail -->) 3 --> 2 --> 1 --> 0 (--> head) (右边为头结点,元素存的是下标)
 */
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        ArrayDeque<Integer> deque = new ArrayDeque<>();
        int n = nums.length;
        int[] res = new int[n - k + 1];
        int idx = 0;
        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){
                res[idx++] = nums[deque.peek()];
            }
        }
        return res;
    }
}

4.题目总结

解题思路里

347.前 K 个高频元素(!!!)

题目链接:前K个高频元素

1.解题思路

用map,key来存放元素,value来存放次数

用value排序,取数量前k个的key值

大顶堆和小顶堆特别擅长在一个很大的数据集里求前k个高频或低频元素

2.我的实现

实现遇到的问题

代码

public int[] topKFrequent2(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>(); //key为数组元素值,val为对应出现次数
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) { //小顶堆只需要维持k个元素有序
            if (pq.size() < k) { //小顶堆元素个数小于k个时直接加
                pq.add(new int[]{entry.getKey(), entry.getValue()});
            } else {
                if (entry.getValue() > pq.peek()[1]) { //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
                    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;
    }

3.标准代码

public int[] topKFrequent2(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>(); //key为数组元素值,val为对应出现次数
        for (int num : nums) {
            map.put(num, map.getOrDefault(num, 0) + 1);
        }
        //在优先队列中存储二元组(num, cnt),cnt表示元素值num在数组中的出现次数
        //出现次数按从队头到队尾的顺序是从小到大排,出现次数最低的在队头(相当于小顶堆)
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1, pair2) -> pair1[1] - pair2[1]);
        for (Map.Entry<Integer, Integer> entry : map.entrySet()) { //小顶堆只需要维持k个元素有序
            if (pq.size() < k) { //小顶堆元素个数小于k个时直接加
                pq.add(new int[]{entry.getKey(), entry.getValue()});
            } else {
                if (entry.getValue() > pq.peek()[1]) { //当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
                    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;
    }

4.题目总结

解题思路里面

  • 23
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
第二十二算法训练营主要涵盖了Leetcode题目中的三道题目,分别是Leetcode 28 "Find the Index of the First Occurrence in a String",Leetcode 977 "有序数组的平方",和Leetcode 209 "长度最小的子数组"。 首先是Leetcode 28题,题目要求在给定的字符串中找到第一个出现的字符的索引。思路是使用双指针来遍历字符串,一个指向字符串的开头,另一个指向字符串的结尾。通过比较两个指针所指向的字符是否相等来判断是否找到了第一个出现的字符。具体实现的代码如下: ```python def findIndex(self, s: str) -> int: left = 0 right = len(s) - 1 while left <= right: if s[left == s[right]: return left left += 1 right -= 1 return -1 ``` 接下来是Leetcode 977题,题目要求对给定的有序数组中的元素进行平方,并按照非递减的顺序返回结果。这里由于数组已经是有序的,所以可以使用双指针的方法来解决问题。一个指针指向数组的开头,另一个指针指向数组的末尾。通过比较两个指针所指向的元素的绝对值的大小来确定哪个元素的平方应该放在结果数组的末尾。具体实现的代码如下: ```python def sortedSquares(self, nums: List[int]) -> List[int]: left = 0 right = len(nums) - 1 ans = [] while left <= right: if abs(nums[left]) >= abs(nums[right]): ans.append(nums[left ** 2) left += 1 else: ans.append(nums[right ** 2) right -= 1 return ans[::-1] ``` 最后是Leetcode 209题,题目要求在给定的数组中找到长度最小的子数组,
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值