文章:代码随想录
状态:比较难,主要理解思路
leecode 239 滑动窗口的最大值
思路:
用单调队列为最优解,用堆实现时间复杂度则为O(Nlogn)。我们始终要维护队列的头部元素始终是当前窗口的最大值,并且保持窗口内保持单调递减,比他小的元素可以直接从后面退掉。从后面退掉更容易实现。
代码:
有几种实现方式:
//方式一,自定义队列
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();}
}
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums.length == 1) {
return nums;
}
Myqueue myqueue=new Myqueue();
int [] result=new int[nums.length-k+1];
int index=0;
//先将前k的元素放入队列
// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
for (int i = 0; i < k; i++) {
myqueue.add(nums[i]);
}
result[index++]=myqueue.peek();
//i代表的是窗口尾部
for (int i = k; i < nums.length; i++) {
//窗口每移动一次,头部就要pop掉i-k+1上一个位置的值,尾部要加入下一个当前i位置的值
myqueue.poll(nums[i-k]);
myqueue.add(nums[i]);
result[index++]= myqueue.peek();
}
return result;
}
接下来是双端队列的单指针和双指针:
//方法二利用双端队列手动实现单调队列
//用一个单调队列来存储对应的下标,每当窗口滑动的时候,直接取队列的头部指针对应的值放入结果集即可
//这是下标版本,pop的时候可以通过下标来判断判断头节点是否需要弹出.
public int[] maxSlidingWindowD(int[] nums, int k) {
Deque<Integer> deque=new ArrayDeque<>();
int[] result =new int[nums.length-k+1];
int index=0;
//i代表的是窗口尾部
for (int i = 0; i < nums.length; i++) {
//1.队列头结点需要在[i - k + 1, i]范围内,不符合则要弹出
//为什么if可以而不是while呢?因为后面的元素下标一定是在当前头元素之后的。
// 如果元素下标比当前头元素小,证明这个元素先加入队列,那么当这个头元素加入进来的时候,这些比头元素小的值早就被remove了,所以不可能存在值既比当前头元素值小,下标也比他小。
//所以不会存在错过的情况,值也是同理,只需要比对一次.但是值不能用while,因为会存在值相同的情况,那就会移除多个元素导致数值不准确.下标不会重复
if (!deque.isEmpty() && deque.peek() < i - k + 1){deque.poll();}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while (!deque.isEmpty() && nums[(deque.getLast())]<nums[i] ){
deque.removeLast();
}
//加入下标
deque.offer(i);
/// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
if(i>=k-1){
result[index++]=nums[deque.peek()];
}
}
return result;
}
//双端队列加双指针,值版本
public int[] maxSlidingWindowDbPointer(int[] nums, int k) {
Deque<Integer> deque=new ArrayDeque<>();
int[] result =new int[nums.length-k+1];
int index=0;
//i代表的是窗口尾部,j代表窗口头部
for (int i = 0,j=i-k+1; i < nums.length; i++,j++) {
//1.判断队列头部的元素是否是当前窗口头部上一个位置的值,如果是则证明需要弹出.如果不是则证明当前窗口的值是后面的值,现阶段无需弹出
//和之前自定义队列的弹出相同
if (!deque.isEmpty() && j>0 && deque.peek() ==nums[j-1]){deque.poll();}
// 2.既然是单调,就要保证每次放进去的数字要比末尾的都大,否则也弹出
while (!deque.isEmpty() && deque.getLast()<nums[i] ){
deque.removeLast();
}
//加入值
deque.offer(nums[i]);
/// 因为单调,当i增长到符合第一个k范围的时候,每滑动一步都将队列头节点放入结果就行了
//j等于0时候,第一个k已经遍历完
if(j>=0){
result[index++]=deque.peek();
}
}
return result;
}
Leecode 347 前k个高频元素
思路:
这个主要是要知道用堆来实现.那么可以改变优先队列中的对比器来用大顶堆或者小顶堆来实现。
代码:
//小顶堆实现.小顶堆的思路是,只保留最大的前k个元素,每次添加时将这k个中最小的元素弹出.
public int[] topKFrequent2(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<>(new Comparator<int[]>() {
// @Override
// public int compare(int[] o1, int[] o2) {
// return o1[1]-o2[1];
// }
// });
//lambda表达式写法
// PriorityQueue<int[]> pq = new PriorityQueue<>((int[]a,int[]b)->{return a[1]-b[1];});
PriorityQueue<int[]> pq = new PriorityQueue<>((a,b)->a[1]-b[1]);
for(Map.Entry<Integer,Integer> entry:map.entrySet()){//小顶堆只需要维持k个元素有序
if(pq.size()<k){//小顶堆元素个数小于k个时直接加
pq.add(new int[]{entry.getKey(),entry.getValue()});
}else{
if(entry.getValue()>pq.peek()[1]){//当前元素出现次数大于小顶堆的根结点(这k个元素中出现次数最少的那个)
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;
}
//解法2:基于大顶堆实现 大顶堆的思路是把所有元素排序然后只pop前k个.
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;
}