代码随想录第十三天

内容:

  1. 逆波兰表达式(150)
  2. 滑动窗口最大值(239)
  3. 前K个高频元素(347)
  4. 栈与队列总结

1.逆波兰表达式求值

难度:🔥🔥

1.1 思路分析

我们需要知道这道题是如何使用栈结构进行计算的,其实逆波兰表达式相当于二叉树的后序遍历,我们遇到数据就将该数据加入到栈中,遇到运算符就从栈顶弹出两个数据运算再加入到栈中,直到遍历到最后就得到了我们想要的值。

由于这题所给的数据都是合法的,我们就不需要去处理异常情况了。

如图:

150.逆波兰表达式求值
1.2 代码实现
class Solution {
    //题目所给数据都是合法的
    public int evalRPN(String[] tokens) {
        Deque<Integer> deque = new ArrayDeque<>();
        for(String s : tokens){
            if ("+".equals(s)){
                deque.push(deque.pop() + deque.pop());
            }else if("-".equals(s)){//先进栈的数据减去后进栈的数据
                deque.push(-deque.pop() + deque.pop());
            }else if("*".equals(s)){
                deque.push(deque.pop() * deque.pop());
            }else if("/".equals(s)){
                int nums1 = deque.pop();
                int nums2 = deque.pop();
                deque.push(nums2 / nums1);
            }else {
                deque.push(Integer.valueOf(s));
            }
        }
        return deque.pop();
    }
}
1.3 收获总结
  • 后缀表达式对于计算机来说是十分友好的,而我们人更习惯于中缀表达式

2.滑动窗口最大值

难度:🔥🔥🔥🔥🔥🔥

1.1 思路分析

本题较难,本次刷题先跳过

1.2 代码实现
//解法一
//自定义数组
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;
    }
}

1.3 收获总结

3.前K个高频元素

难度:🔥🔥🔥🔥

1.1 思路分析

这道题目就引出了优先队列的使用

存储数组中的值以及对应值出现的次数是Map的拿手好戏,接下来我们可以对Map的Value进行排序,即可得到前K个高频元素。

那我们可不可以只对前K个元素进行排序呢?答案是可以的。堆这种数据结构就可以实现,堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。而大顶堆虽然看似符合我们要找前K个高频元素的要求,但如果只对K个元素进行排序,那么大顶堆就会优先poll()出数值大的元素,剩下的是低频元素,所以我们使用小顶堆。

但队列中的元素小于 K 个时,我们直接添加。反之我们判断堆顶元素与要添加值的大小,再判断是否添加。

如图:

347.前K个高频元素
1.2 代码实现

基于小顶堆实现

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        //使用Map和小顶堆来解决这个问题
        Map<Integer,Integer> map = new HashMap<>();
        //将我们的键值对放入map中
        for(int n : nums){
			//Map.getOrDefault(key,默认值);
			//Map中会存储一一对应的key和value。
			//如果在Map中存在key,则返回key所对应的的value。
			//如果在Map中不存在key,则返回默认值。
            map.put(n,map.getOrDefault(n,0) + 1);
        }
        //按照小顶堆排序,小的value放在上面
        PriorityQueue<int[]> queue = new PriorityQueue<>((pair1,pair2) -> pair1[1] - pair2[1]);
        //遍历map,添加到优先队列中,entrySet()可以直接获取对应的 Key 和 Value
        for(Map.Entry<Integer,Integer> entry : map.entrySet()){
            //如果队列中的元素小于k个,我们直接添加
            if (queue.size() < k){//以二元数组形式添加
                queue.add(new int[]{entry.getKey(),entry.getValue()});
            }else {//反之,如果我们要添加的Value大于堆顶的Value
                if (entry.getValue() > queue.peek()[1]){
                    queue.poll();
                    queue.add(new int[]{entry.getKey(),entry.getValue()});
                }
            }
        }
        //按照高频在前,低频在后的顺序
        int[] result = new int[k];
        for(int i = k - 1;i >= 0;i--){
            result[i] = queue.poll()[0];
        }
        return result;
    }
}
1.3 收获总结
  • 理解什么是大顶堆,小顶堆以及什么时候该使用大顶堆,小顶堆。

4.栈与队列总结

第二十二天的算法训练营主要涵盖了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题,题目要求在给定的数组中找到长度最小的子数组,使得子数组的和大于等于给定的目标值。这里可以使用滑动窗口的方法来解决问题。使用两个指针来表示滑动窗口的左边界和右边界,通过移动指针来调整滑动窗口的大小,使得滑动窗口中的元素的和满足题目要求。具体实现的代码如下: ```python def minSubArrayLen(self, target: int, nums: List[int]) -> int: left = 0 right = 0 ans = float('inf') total = 0 while right < len(nums): total += nums[right] while total >= target: ans = min(ans, right - left + 1) total -= nums[left] left += 1 right += 1 return ans if ans != float('inf') else 0 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口等算法的理解和应用能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值