所用代码 java
滑动窗口最大值 LeetCode 239
题目链接:滑动窗口最大值 LeetCode 239 - 困难
思路
无。
试了一下暴力,会超时:
public int[] maxSlidingWindow(int[] nums, int k) {
// 新数组长度为nums.length - k + 1
int[] res = new int[nums.length - k + 1];
int temp = Integer.MIN_VALUE;
for (int i = 0; i < nums.length - k + 1; i++) {
temp = Integer.MIN_VALUE;
for (int j = i; j < i + k; j++) {
if (nums[j] > temp){
temp = nums[j];
}
}
res[i] = temp;
}
return res;
}
正确做法:单调队列 自定义的一个队列,实现我们需要的pop 、push 和getMaxValue
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
int n = 0;
MyQueue myQueue = new MyQueue();
int[] res = new int[nums.length - k + 1];
// 先将前k个元素放入队列,得到第一个最大的数
for (int i = 0; i < k; i++) {
myQueue.push(nums[i]);
}
res[n++] = myQueue.getMaxValue();
// 从k后面添加窗口元素,添加一个移动一次窗口
for (int i = k; i < nums.length; i++) {
// 添加之前先移除窗口的第一个值 (1 3 -1 把1移除,实际没有1 )
myQueue.pop(nums[i-k]);
// 添加第k+1个元素
myQueue.push(nums[i]);
// 获取最大值
res[n++] = myQueue.getMaxValue();
}
return res;
}
}
// 自定义一个单调队列
class MyQueue{
Deque<Integer> deque = new ArrayDeque<>();
// 如果队列的第一个元素等于队头的最大值就弹出
public void pop(int val){
if (!deque.isEmpty() && val == deque.peekFirst()){
deque.pollFirst();
}
}
// 从队列尾部加入一个值,该值保证队列从后往前都比它小
public void push(int val){
while (!deque.isEmpty() && val > deque.peekLast()){
deque.pollLast();
}
// deque为空或是前面都比val小了直接在后面加入
deque.offerLast(val);
}
// 获取队列最大的值,即队列的第一个元素
public int getMaxValue(){
return deque.peekFirst();
}
}
总结
若这种题实在没办法就暴力得一点点分。
单调队列这种方法不难,难的是如何想到用这种方法,并把它实现出来
力扣官方有优先队列的解法,感觉还是很不错:
public int[] maxSlidingWindow(int[] nums, int k) {
// 使用优先队列
// int数组两种值,第一个是num,第二个是索引
PriorityQueue<int[]> pq = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 维护大顶堆, 数值不相等第一个数就是最大的,相等就是索引较大的
return o1[0] != o2[0] ? o2[0] - o1[0] : o2[1] - o1[1];
}
});
//先存k个数进入堆
for (int i = 0; i < k; i++) {
pq.offer(new int[]{nums[i], i});
}
int n = nums.length;
int index = 0;
int[] res = new int[n - k + 1];
// 赋值第1个数
res[index++] = pq.peek()[0];
// 存k到n-k+1个数
for (int i = k; i < n; i++) {
// 1、存第k+1个数
pq.offer(new int[]{nums[i], i});
// 2、判断堆顶元素的索引是不是已被移除
// 也就是窗口移动后,该数应被删除
while (pq.peek()[1] <= i - k){
pq.poll();
}
// 3、堆顶的最大值存入数组
res[index++] = pq.peek()[0];
}
return res;
}
前K个高频元素 LeetCode 347
题目链接:前 K 个高频元素 LodeCode 347 - 中等
思路
用map存每个数据key为数字,value为出现的次数。
代码随想录里面提出我们可以用大顶堆和小顶堆来解决求前k个高频或是低频的操作,也就是优先队列操作(PriorityQueue)这个确实不会,学习了。
难点1: 实现小顶堆方法 (Comparator接口 => o1 - o2 是从小到大排序)
PriorityQueue<Integer> heap=new PriorityQueue<Integer>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
// o2-o1 为大顶堆
return o1-o2;
}
});
lamda表达式 小顶堆
PriorityQueue<Integer> heap=new PriorityQueue<Integer>( (num1,num2) -> return num1-num2)
难点2: 遍历map方式
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
//得到key和value
entry.getKey()
entry.getValue()
}
小顶堆实现: 维护k个有序元素
public int[] topKFrequent(int[] nums, int k) {
// 每个map里面的值为 元素=>出现的次数
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
//map.getOrDefault(k,default),取key的value,没有就默认值default
map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
}
// 创建小顶堆,堆中的值为int[]数组
// 第一个为nums出现元素,第二个值为该元素出现的次数
PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 比较的是次数,所以是数组的第二个数
return o1[1]-o2[1];
}
});
// 小顶堆需维护k个元素有序
for (Map.Entry<Integer,Integer> entry: map.entrySet()){
// kv[0]为元素,kv[1]为该元素出现的次数,共两个值
int[] kv = new int[]{entry.getKey(),entry.getValue()};
// 小顶堆个数小于k个时直接添加
if (heap.size() < k){
heap.add(kv);
}else {
// 若map里面存的下一个数大于堆顶的元素,就弹出堆顶元素并加入新元素
if (entry.getValue() > heap.peek()[1]){
// 弹出顶堆,即最小的那个数,剩下就是最大的数
heap.poll();
heap.add(kv);
}
}
}
int[] res = new int[k];
// 以此弹出小顶堆,注意是从小到大弹出的
// 从大到小的话就逆向赋值
for (int i = k - 1;i >= 0;i--){
// 需要的是数组的第一个值
res[i] = heap.poll()[0];
}
return res;
}
大顶堆实现:取前k个元素,相当于维护了所有元素,耗时更慢
public int[] topKFrequent(int[] nums, int k) {
// 每个map里面的值为 元素=>出现的次数
Map<Integer,Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
//map.getOrDefault(k,default),取key的value,没有就默认值default
map.put(nums[i],map.getOrDefault(nums[i],0) + 1);
}
// 创建大顶堆,堆种的值为int[]数组
// 第一个为nums出现元素,第二个值为该元素出现的次数
PriorityQueue<int[]> heap = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
// 比较的是次数,所以是数组的第二个数
return o2[1]-o1[1];
}
});
// 大顶堆需维护所有有序元素
for (Map.Entry<Integer,Integer> entry: map.entrySet()){
// kv[0]为元素,kv[1]为该元素出现的次数,共两个值
int[] kv = new int[]{entry.getKey(),entry.getValue()};
// 所有元素加入大顶堆
heap.add(kv);
}
int[] res = new int[k];
// 直接取大顶堆前k个值
for (int i = 0;i < k;i++){
// 需要的是数组的第一个值
res[i] = heap.poll()[0];
}
return res;
}
总结
了解了优先队列,也就是堆的使用方法,第二就是知道了map的遍历方法。但是还需要多多熟悉这些方法,才能运用自如。