- 自我尝试
- 思路: 使用queue来维系窗口,每个窗口里再进行流排序去max操作。这个暴力法明显超时了。
- 代码
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { Deque<Integer> q = new ArrayDeque<>(); int[] result = new int[nums.length-k+1]; for(int i=0; i<nums.length;i++){ q.add(nums[i]);// 保持两个,进去到三个 if(q.size()==k){ result[i-k+1]=q.stream().mapToInt(Integer::intValue).max().getAsInt(); q.poll(); } } return result; } }
- 收获
- stream:流操作可以串联一起形成一个流水线,每个操作将从其前一个操作中接收数据、处理数据,并将结果发送到下一个操作。操作分为两类:中间与终端操作。
- 中间操作: 操作返回一个新的流,将中间操作串联在一起。例如mapToInt()是一个中间操作,将其转为Intstream流。
- 一般来说就是那个传入函数参数的方法为中间操作。例如map(String::valueOf)
- 终端操作: 终端操作完成流处理并生成结果。它不返回流,而是返回非流值,一旦执行终端操作流就不能再被使用。max()返回最大值就是一个终端操作。
- 返回值类,例如.max().getAsInt()返回int值。
- 返回集合类,例如.collect(Collectors.joining())
- 中间操作: 操作返回一个新的流,将中间操作串联在一起。例如mapToInt()是一个中间操作,将其转为Intstream流。
- stream:流操作可以串联一起形成一个流水线,每个操作将从其前一个操作中接收数据、处理数据,并将结果发送到下一个操作。操作分为两类:中间与终端操作。
- 范例后的自我尝试
- 思路:实现一个单调队列,通过自我定义的push,pop,以及getMaxValue方法来实现期许的结果。
- 主函数思路:通过遍历整个nums,调用三个定义的操作来获取最后的结果。
- headsup1:push操作每次都要执行。
- headsup2:pop操作只在元素积累到k+1个时候才开始pop。
- headsup3:result的添加需要在元素积累到k个时候就开始。
- push函数思路:当队列里尾部元素小于当前元素时候不断移除,随后添加进去。
- headsup1:当待加元素等于尾部元素时候,也添加进入队列。这样的好处是在pop的时候方便移除。
- pop函数思路:当传入的元素值与队列头元素相同时候移除
- headsup1:这个传入元素是哪一个要清楚!
- getMaxValue函数思路: 直接调用deque自带的peekFirst()获取
- 主函数思路:通过遍历整个nums,调用三个定义的操作来获取最后的结果。
- 代码
class Solution { public int[] maxSlidingWindow(int[] nums, int k) { Deque<Integer> q = new ArrayDeque<>(); int[] result = new int[nums.length-k+1]; for(int i=0; i<nums.length;i++){ push(nums[i],q); // System.out.println("--- "+i +"th round ---"); // System.out.println(q.toString()); if(i-k>=0){ pop(nums[i-k],q); // System.out.println(q.toString()); } if(i-k+1>=0){ result[i-k+1]=getMaxValue(q); } } return result; } public void push(int val, Deque<Integer> q){ //在push的时候pop掉小于等于val的元素,维护队列的单调性 while(!q.isEmpty()&&q.getLast()<val){ q.pollLast(); } q.add(val); } public void pop(int val, Deque<Integer> q){ //当被遍历的元素是queue出口端的元素时候,才pop if(val == q.getFirst()){ q.pollFirst(); } } public int getMaxValue(Deque<Integer> q){ return q.peekFirst(); } }
- 收获
- Deque使用方法:
- 添加元素: offerFirst(E e)/ offerLast(E e);【注:若无法插入返回false】
- 检索头/尾元素: peekFirst()/peekLast(); 【注:若队列为空返回null】
- 移除元素: pollFirst()/pollLast();【注:若队列为空返回null】
- Deque使用方法:
- 思路:实现一个单调队列,通过自我定义的push,pop,以及getMaxValue方法来实现期许的结果。
347.前 K 个高频元素 【优先级队列】
- 范例题解
- 思路: 通过map来获取频率后,通过一个小顶堆的priorityQueue来维护住k个元素,即通过与堆顶相比,更小的一方出局。最终留下的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<>((pair1,pair2)->pair1[1]-pair2[1]); //遍历map,添加队列元素 for(Map.Entry<Integer,Integer> entry: map.entrySet()){ if(pq.size()<k){ pq.add(new int[]{entry.getKey(),entry.getValue()}); }else{ if(entry.getValue()>pq.peek()[1]){ pq.poll();//移除优先级最高的元素 pq.add(new int[]{entry.getKey(),entry.getValue()}); } } } //传递最后值来return int[] ans = new int[k]; for(int i=k-1;i>=0; i--){ ans[i] = pq.poll()[0]; } return ans; } }
- 收获:
- priorityQueue:一个数据类型,它支持插入新元素和按照优先级删除元素的操作。
- 创建:PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
- 这里的lambda表达式定义优先级队列中元素的排序方式。它返回了一个comparator对象。
- 如果pair1[1]-pair2[1]<0,结果为负数,表明第一个对象应该排在第二个对象之前。
- 相反如果为正数,第一个要排在第二个之后。
- 与堆的关系:
- 堆是实现优先队列的常用办法。通过堆,在o(logn)的时间里完成优先级队列的插入和删除。
- 创建:PriorityQueue<int[]> pq = new PriorityQueue<>((pair1,pair2)->pair1[1]-pair2[1]);
- Entry与entrySet的关系:
- Entry是java.util.map的一个接口,每个entry对象包含一个key与一个value,可通过getKey()与getValue()来获得
- "Map.Entry<CLASSA,CLASSB>" 提供了方便的方式来访问它们
- entrySet也是java.util.map的一个方法,它返回一个包含所有entry的set集合。
- 堆:
- 结构:完全二叉树,高度为logn。最后一层从左到右排布node
- 操作:
- 插入:O(logn):将元素放在堆尾,然后上浮到合适位置,最多logn次。
- 查询:O(1):一般只查根节点。
- 删除:O(logn):将底层最后一个元素放到root,然后下沉恢复堆的属性。
- priorityQueue:一个数据类型,它支持插入新元素和按照优先级删除元素的操作。