代码随想录算法训练营第十三天|239. 滑动窗口最大值、347.前 K 个高频元素

239. 滑动窗口最大值

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

        思路就是代码随想录里的,单调队列,关键是入队时的细节,但是在用Java自带的Deque时,遇到一个问题,一开始,对于单调队列中,pop的实现,我直接调用Deque的pop()方法(本意是想出队),最终导致运行结果不对,看了卡哥代码里用的是poll()方法,这才是出队的方法。其实,我是想用Queue中的出队方法,结果用成了栈中的弹出栈方法(pop)所以导致结果出错,同样的,如果要入队,也应该用add或者offer,而不能用对应栈的入栈方法push!Deque方法我也在我第十天打卡的文章中写了

代码:

class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
        MyQueue myqueue = new MyQueue();
        int[] result = new int[nums.length - k + 1];
        int count = 0;
        for(int i = 0; i < k; i++) {		// 先把第一个窗口的数据全部入队
            myqueue.push(nums[i]);
        }
        result[count] = myqueue.getFront();		// 获取第一个窗口的最大值
        myqueue.pop(nums[count++]);				// 先让第一个数据出队,才可以开始下一次的窗口移动
        // 下面开始循环移动窗口
        for(int i = k; i < nums.length; i++) {
            myqueue.push(nums[i]);
            result[count] = myqueue.getFront();
            myqueue.pop(nums[count++]);
        }
        return result;
    }
}
class MyQueue {
    Deque<Integer> deque = new LinkedList<>();
    public void pop(int val) {		// 当滑动窗口需要划出的值,刚好也是单调队列的对首值时,才出队
        if(!deque.isEmpty() && deque.getFirst() == val) {
            deque.poll();
        }
    }
    public void push(int val) {		// 入队时,入队的这个值应该一直走到比它大的元素之后,比它小的一律从队尾出队
                                    // 因为,比它小的数字(在它之前),不可能再成为当前这k组数中的最大数,也必然不可能成为下一组中的最大的数,
                                    // 因为队列前面比val小的数,必然要比val先出队,所以在我们自定义的这个“单调队列”中,不再需要维护在队列前,比val小的数
        while(!deque.isEmpty() && deque.getLast() < val) {
            deque.removeLast();
        }
        // 之后将val入队,它前面只可能是比它大的数,或者没有   
        deque.offer(val);
    }
    public int getFront() {
        return deque.getFirst();
    }
}

347.前 K 个高频元素

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

        第一次接触,呜呜呜,不太熟悉Java中如何使用优先级队列(大/小顶堆),直接抄代码随想录代码了,但是总结了一下PriorityQueue的常用方法,还有给代码加上了自己的理解注释,加油!

含详细注释的代码:

class Solution {
    public int[] topKFrequent(int[] nums, int k) {
        Map<Integer,Integer> map = new HashMap<>();
        for(int num : nums) {           // 给数字出现的次数设置map键值对
            map.put(num, map.getOrDefault(num,0) + 1);
        }
        // 设置优先级队列(小顶堆),堆中存放的是一个一维数组,且是一个二元组,数组第一个元素是nums中的数字,第二个元素是数字出现的次数
        PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2) -> pair1[1] - pair2[1]);
        for(Map.Entry<Integer,Integer> entry : map.entrySet()){     // 遍历map
            if(pq.size() < k){      // 小于k是直接入队
                pq.add(new int[]{entry.getKey(),entry.getValue()});
            }else{
                if(entry.getValue() > pq.peek()[1]){        // 如果,当前这个元素的值大于堆顶的值,则把堆顶的元素弹出,那么堆中剩下的就是前k个高频的数了(其中,值是指map中的“值”--数字出现的次数,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;
    }
}

PriorityQueue常用方法总结

  1. add(E e):将指定的元素插入此队列中。如果成功插入,则返回true;如果队列已满并且不能容纳更多元素,则抛出IllegalStateException。
  2. offer(E e):将指定的元素插入此队列中。如果成功插入,则返回true;如果队列已满并且不能容纳更多元素,则返回false。
  3. remove():检索并删除此队列的头部元素,如果此队列为空,则抛出NoSuchElementException。
  4. poll():检索并删除此队列的头部元素,如果此队列为空,则返回null。
  5. element():检索但不删除此队列的头部元素,如果此队列为空,则抛出NoSuchElementException。
  6. peek():检索但不删除此队列的头部元素,如果此队列为空,则返回null。
  7. size():返回此队列中的元素数量。
  8. isEmpty():检查此队列是否为空。
  9. iterator():返回一个迭代器,该迭代器将按升序顺序依次访问此队列中的元素。
  10. toArray():返回包含此队列中所有元素的数组。
  11. clear():移除此队列中的所有元素。

 有关优先级队列创建时的语法问题:

一开始对下面这段代码是一脸懵逼的:

PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2) -> pair1[1] - pair2[1]);

 其实这就是一个设置优先级队列为“小顶堆”的创建方式,通过给构造方法传入自定义比较器有关优先级队列设置比较器的详解,这里转载了一位大佬的讲解)的方式设置。也就是这段代码:

(pair1,pair2) -> pair1[1] - pair2[1]

而上面这段代码就是一段Java8新增的lambda表达式,理解为简单的函数,参数是pair1和pair2,类型省略了与前面的泛型定义的一致,是整型数组int[],方法体是pair1[1] - pair2[1],返回值也是方法体,因为比较简单,return直接省略了,而pair1[1]就是入队的二元组中的第二个元素,是数字出现的次数。详细的写法应该是这样的:

(int[] pair1, int[] pair2)->{
        return pair1[1] - pair2[1];
};

总结:lambda就是一种简单的式子,一种让函数作为参数更加简洁的式子。有关更多也转载了一篇大佬的文章

  • 9
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
第二十二天的算法训练营主要涵盖了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 ``` 以上就是第二十二天的算法训练营的内容。通过这些题目的练习,可以提升对双指针和滑动窗口算法的理解和应用能力。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值