【刷题day13】栈与队列 | 239. 滑动窗口最大值 、347.前 K 个高频元素、总结

239. 滑动窗口最大值

题目链接/文章讲解/视频讲解

思路:

  1. 暴力法:遍历各个滑动窗口,从中取出最大值(O(n × k)超出时间限制)

  2. 单调队列法:设计单调队列的时候,pop()push() 操作要保持如下规则:

    • pop(value):如果窗口移除的元素value等于单调队列的出口元素(维护的最大值),那么队列弹出元素,否则不用任何操作;
    • push(value):如果push的元素value大于入口元素的数值,那么就将队列入口的元素逐个弹出,直到push元素的数值小于等于队列入口元素的数值为止。(注意是从后往前poll,保证队列递减)

    保持如上规则,每次窗口移动的时候,只要 que.front() 就可以返回当前窗口的最大值。为了更直观的感受到单调队列的工作过程,以题目示例为例,输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3,动画如下:
    在这里插入图片描述

首先要明确的是,题解中单调队列里的pop和push接口,仅适用于本题哈。单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。 不要以为本题中的单调队列实现就是固定的写法。

// 1. 暴力法:超出时间限制
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
    	ArrayList<Integer> ans = new ArrayList<Integer>();
    	int left=0, right=k-1;
    	while(right < nums.length) {
    		int max = Integer.MIN_VALUE;
    		for(int i=left; i<=right; i++) {
    			max = max > nums[i] ? max : nums[i];
    		}
    		ans.add(max);
    		left++;
    		right++;
    	}
    	int[] res = new int[ans.size()];
    	int index = 0;
    	for(Integer n: ans) {
    		res[index++] = n;
    	}
    	return res;
    }
}

// 2. 单调队列法:
class Solution {
    public int[] maxSlidingWindow(int[] nums, int k) {
    	int[] ans = new int[nums.length - k + 1]; // 滑动窗口数为结果数组大小
    	Deque<Integer> dq = new LinkedList<Integer>();
    	//将第一个滑动窗口存入单调队列
    	for(int i=0; i<k; i++) { 
    		while(!dq.isEmpty() && nums[i]>dq.peekLast()) {
    		//注意是从后往前维护,这也是为什么要用到双端队列
    			dq.pollLast();
    		}
    		dq.offerLast(nums[i]);
    	}
    	// 存入第一个窗口最大值
    	int index = 0;
    	ans[index++] = dq.peekFirst(); 
    	//窗口开始向后移动
    	for(int j=k; j<nums.length; j++) { 
    		// 若窗口要移除元素正好是单调队列出口的元素(所维护的最大值),将其移出队列
    		if(dq.peekFirst() == nums[j-k]) dq.pollFirst(); 
    		// 新元素存入,并维护单调队列
    		while(!dq.isEmpty() && nums[j]>dq.peekLast()) {
    			dq.pollLast();
    		}
    		dq.offerLast(nums[j]);
    		ans[index++] = dq.peekFirst();
    	}
    	return ans;
    }
}

347.前 K 个高频元素

题目链接/文章讲解/视频讲解

思路:

  1. 利用Map存储:key为元素值,value对应出现次数,然后对map中元素按照value值排序取前k个即可。注意自定义排序的写法
  2. 利用优先级队列:其实就是一个披着队列外衣的,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆
// 方法一:HashMap
class Solution347_ {
    public int[] topKFrequent(int[] nums, int k) {
    	int[] ans = new int[k];
    	Map<Integer, Integer> mp = new HashMap<Integer, Integer>();
    	// 存入map,key为元素值,value对应出现次数
    	for(int n: nums) {
    		mp.put(n, mp.getOrDefault(n, 0) + 1);
    	}
    	//对map中元素按照value值排序并存入list中
    	List<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer,Integer>>(mp.entrySet());
    	Comparator<Map.Entry<Integer, Integer>> valueComparator = new Comparator<>() {
			@Override
    		//compare方法,当方法的返回值大于0的时候就将列表的前一个数o1和后一个数o2做交换
			public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
				// 倒序排序后减前
				return o2.getValue() - o1.getValue();
			}
		}; 
		Collections.sort(list, valueComparator);
		for(int i=0; i<k; i++) {
			ans[i] = list.get(i).getKey();
		}
    	return ans;
    }
}
// 方法二:优先级队列
class Solution {
    //解法1:基于大顶堆实现
    public int[] topKFrequent1(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)->pair2[1]-pair1[1]);
        for(Map.Entry<Integer,Integer> entry:map.entrySet()){//大顶堆需要对所有元素进行排序
            pq.add(new int[]{entry.getKey(),entry.getValue()});
        }
        int[] ans = new int[k];
        for(int i=0;i<k;i++){//依次从队头弹出k个,就是出现频率前k高的元素
            ans[i] = pq.poll()[0];
        }
        return ans;
    }

总结

文章总结

自定义排序

利用 Collections.sort() 方法,传入 List 和 Comparator对象两个参数,注意要重写Comparator对象中的compare方法。

  • 注意:若要调用compareTo,必须是包装类型,若为int则需要Integer.valueOf()转为Integer。
  • 注意:建议使用compareTo而不是直接减。eg.若被减数为int最小值-2147483648,减去1则会变成int的最大值2147483647,会出错的!!!
//对Map中元素按照value值倒序排序并存入list中
System.out.println(mp);// {1=3, 2=1, 3=6, 4=2, 5=4, 6=5}
//注意存入List中的写法
ArrayList<Map.Entry<Integer, Integer>> list = new ArrayList<Map.Entry<Integer, Integer>>(mp.entrySet());
Comparator<Map.Entry<Integer, Integer>> valueComparator = new Comparator<Map.Entry<Integer, Integer>>() {
    @Override
    //compare方法,当方法的返回值大于0的时候就将列表的前一个数o1和后一个数o2做交换
    public int compare(Map.Entry<Integer, Integer> o1, Map.Entry<Integer, Integer> o2) {
        //注意此处o1与o2位置
        //指定的数与参数比较
        //相等返回0; 小于参数返回 -1; 大于参数返回 1
        return o2.getValue().compareTo(o1.getValue());
        //return o2.getValue()-o1.getValue();
    }
};
Collections.sort(list, valueComparator);	
System.out.println(list); // [3=6, 6=5, 5=4, 1=3, 4=2, 2=1]

● 利用优先队列

//在优先队列中存储二元组(num,cnt),cnt表示元素值num在数组中的出现次数
//出现次数按从队头到队尾的顺序从大到小排,出现次数最多的在队头(相当于大顶堆)
PriorityQueue<int[]> pq = new PriorityQueue<>((o1, o2) -> o2[1] - o1[1]);
for(Map.Entry<Integer,Integer> entry: mp.entrySet()){//存入优先队列
    pq.add(new int[]{entry.getKey(), entry.getValue()});
}
for(int i=0; i<mp.size(); i++) {
    System.out.print(Arrays.toString(pq.poll())+" "); 
} // [3, 6] [6, 5] [5, 4] [1, 3] [4, 2] [2, 1] 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值