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

61 阅读1分钟

239. 滑动窗口最大值

链接

题目链接

文章链接

第一想法

看到这道题,发现也是取窗口最大的,想法还是单调队列,不过有些不同,先需要比较初始状态下的滑动窗口里面的最大值,并用stack存储下标(从大到小),则stack[0]的位置就是当前滑动窗口的最大值,代码如下:

  let stack:number[]=[] //单调队列
  let res:number[]=[] //存放最终结果
  for(let i=0;i<k;i++){ //初始滑动窗口  这里类似单调队列
      while(stack.length>0&&nums[stack[stack.length-1]]<nums[i]){//这里比较的是下标对应nums数值的大小
          stack.pop()
      }
      stack.push(i)//存储下标
  }
  res.push(nums[stack[0]]) //将当前滑动窗口的最大值存储

之后开始对窗口进行滑动,且stack[0]一定是滑动窗口的最大值的下标,代码如下:

for(let i=k;i<nums.length;i++){
      if(i-k>=stack[0]) stack.shift(); //这里 是防止这种情况 [100,4,33,1] 当k=3时,且滑动窗口为下标1 2 3时,stack[0]仍是下标0的情况,这里判断i-k>stack[0]是如果滑动
      while(stack.length>0&&nums[stack[stack.length-1]]<nums[i]){
          stack.pop()
      }
      stack.push(i)
      res.push(nums[stack[0]])//将当前滑动窗口的最大值存储
  }
  return res

完整代码如下:

function maxSlidingWindow(nums: number[], k: number): number[] {
  let stack:number[]=[]
  let res:number[]=[]
  for(let i=0;i<k;i++){
      while(stack.length>0&&nums[stack[stack.length-1]]<nums[i]){
          stack.pop()
      }
      stack.push(i)
  }
  res.push(nums[stack[0]])
  for(let i=k;i<nums.length;i++){
      if(i-k>=stack[0]) stack.shift();
      while(stack.length>0&&nums[stack[stack.length-1]]<nums[i]){
          stack.pop()
      }
      stack.push(i)
      res.push(nums[stack[0]])
  }
  return res
};

看完文章后的想法

文章的想法和我的想法是一致,只不过有点区别,他是维护元素而我维护的是下标,同时它先构造了一个队列有助于我们理解,代码如下:

function maxSlidingWindow(nums: number[], k: number): number[] {
  class Queue{
      queue:number[];
      constructor(){
          this.queue=[]
      }
      //入队
      public enqueue(value:number):void{
         while(this.queue.length>0&&this.queue[this.queue.length-1]<value){
             this.queue.pop()
         }
         this.queue.push(value)
      }
      //出队,也就是我上方代码中的i-k>=stack[0]这个条件
      public dequeue(value:number):void{
        if(value===this.queue[0])  this.queue.shift()
      }
      //获取最大元素
      public top():number{
          return this.queue[0]
      }
  }
let stack:Queue=new Queue();
let res:number[]=[]
for(let i=0;i<k;i++){
    stack.enqueue(nums[i])
}
res.push(stack.top())
for(let i=k,j=0;i<nums.length;i++,j++){
   stack.dequeue(nums[j]) //查看stack[0]是否要出队
   stack.enqueue(nums[i])
   res.push(stack.top())
}
return res
};

思考

这道题属于hard,确实很难,但是ts/js没有栈和队列这个概念,数组可以完全模拟栈和队列,所以构造一个类是比较思路清晰一点,完整详细的讲解请看代码随想录中的讲解,很细,这道题得多次回顾查看。

347. 前 K 个高频元素

链接

文章链接

题目链接

第一想法

我首先想到的就是利用哈希表,key值为nums的元素,value为出现的次数,之后再转变为arr,通过sort方法按照次数从大到小排列,之后取前k个元素,代码如下:

function topKFrequent(nums: number[], k: number): number[] {
    let map:Map<number,number>=new Map()
    let res:number[]=[]
    for(let i=0;i<nums.length;i++){
        map.set(nums[i],1+(map.get(nums[i])||0))
    }
    let arr:number[][]=Array.from(map)
    arr.sort((a,b)=>b[1]-a[1])
    for(let i=0;i<k;i++){
        res.push(arr[i][0])
    }
    return res
};

看完文章后的想法

文章利用的是小顶堆,大小为k,但是ts/js里面没有堆这个概念,所以文章中也是用来sort这个函数,这是文章中的ts代码:

function topKFrequent(nums: number[], k: number): number[] {
    const countMap: Map<number, number> = new Map();
    for (let num of nums) {
        countMap.set(num, (countMap.get(num) || 0) + 1);
    }
    // tS没有最小堆的数据结构,所以直接对整个数组进行排序,取前k个元素
    return [...countMap.entries()]
        .sort((a, b) => b[1] - a[1])
        .slice(0, k)
        .map(i => i[0]);
};

思考

代码随想录中我看到了也有自己构造小顶堆的,如果是我单独自己的话,估计很难模拟出来,但是利用小顶堆这个问题的思路我是搞懂了的,总体上说是不难的。但是手写小顶堆得去练习一下。

今日总结

今日总共两道题,第一道比较难,第二道不难,但是如果手写小顶堆的话就比较难了,用时2.5小时。