二叉堆

239. 滑动窗口最大值

给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。

返回 滑动窗口中的最大值

示例 1:

输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
解释:
滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

示例 2:

输入:nums = [1], k = 1
输出:[1]

提示:

  • 1 <= nums.length <= 105
  • -104 <= nums[i] <= 104
  • 1 <= k <= nums.length
解题思路
1. 可以使用前面讲解的单调栈解决
	如果遇到一个比前面的数都要大的数
	前面的元素都没用了,队头就是最大的
	保证了队列为递减序列
	
2.  使用二叉堆,思想是一样的,都是保证最大值在最前面
	即最大值一直在堆顶(最大堆)
二叉堆
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */


function Node(val,index){

    this.val=val
    this.index=index
}

class maxHeap{

    // 构造函数
    constructor(){
        this.heap=[]
    }

    // 交换
    swap(i1,i2){
        let temp=this.heap[i1]
        this.heap[i1]=this.heap[i2]
        this.heap[i2]=temp
    }

    // 获取父节点

    getParentIndex(index){
        return (index-1)>>1
    }

    getLeftIndex(index){
        return index*2+1
    }

    getRightIndex(index){
        return index*2+2
    }

    // 下移操作

    shiftDown(index){

        // 当前节点往下走
        let left=this.getLeftIndex(index)
        let right=this.getRightIndex(index)

        // 左边和右边都有,跟大的换
        if(this.heap[left]&&this.heap[right]){
            
            if(this.heap[left].val>this.heap[right].val&&this.heap[left].val>
                this.heap[index].val
            ){
                this.swap(index,left)
                this.shiftDown(left)
            }else if(this.heap[left].val<=this.heap[right].val&&this.heap[right].val>
                this.heap[index].val){
                this.swap(index,right)
                this.shiftDown(right)
            }
        }else{

            if(this.heap[left]&&this.heap[left].val>
                this.heap[index].val){

                this.swap(index,left)
                this.shiftDown(left)
            }

            if(this.heap[right]&&this.heap[right].val>
                this.heap[index].val){
                
                this.swap(index,right)
                this.shiftDown(right)
            }
        }

        

    }

    // 上移操作

    shiftUp(index){

        if(index===0) return 

        let parent=this.getParentIndex(index)

        if(this.heap[index].val>this.heap[parent].val){
            this.swap(index,parent)
            this.shiftUp(parent)
        }

    }

    // 添加元素
    push(node){
        this.heap.push(node)

        this.shiftUp(this.heap.length-1)
    }

    // 删除堆顶

    pop(){
        if(this.heap.length){
            let top=this.heap[0]
            this.heap[0]=this.heap.pop()
            this.shiftDown(0)

            return top
        }

        return undefined
    }



}

var maxSlidingWindow = function(nums, k) {

    // 使用最大堆

    // 堆顶永远是最大值

    // 到达k时,我们每加入一个节点,就有一个过期,需要删除

    // 但是这个需要删除的节点不一定在堆顶,所以我们只要标记一下就行

    let h=new maxHeap()

    // console.log(h.heap) 

    let res=[]

    for(let i=0;i<k-1;i++){
        let node=new Node(nums[i],i)
        h.push(node)
    }

    // console.log(h.heap)

    for(let i=k-1;i<nums.length;i++){
        let node=new Node(nums[i],i)
        h.push(node)
        // console.log(h.heap)

        // 输出堆顶
        // 判断堆顶是否过期
        // console.log(h.heap[0].index,i-k)
        while(h.heap[0].index<=(i-k)){
            // console.log(1)
            h.pop()
        }
        res.push(h.heap[0].val)
    }

    // console.log(res)


    return res
};

355. 设计推特

设计一个简化版的推特(Twitter),可以让用户实现发送推文,关注/取消关注其他用户,能够看见关注人(包括自己)的最近 10 条推文。

实现 Twitter 类:

  • Twitter() 初始化简易版推特对象
  • void postTweet(int userId, int tweetId) 根据给定的 tweetIduserId 创建一条新推文。每次调用此函数都会使用一个不同的 tweetId
  • List<Integer> getNewsFeed(int userId) 检索当前用户新闻推送中最近 10 条推文的 ID 。新闻推送中的每一项都必须是由用户关注的人或者是用户自己发布的推文。推文必须 按照时间顺序由最近到最远排序
  • void follow(int followerId, int followeeId) ID 为 followerId 的用户开始关注 ID 为 followeeId 的用户。
  • void unfollow(int followerId, int followeeId) ID 为 followerId 的用户不再关注 ID 为 followeeId 的用户。

示例:

输入
["Twitter", "postTweet", "getNewsFeed", "follow", "postTweet", "getNewsFeed", "unfollow", "getNewsFeed"]
[[], [1, 5], [1], [1, 2], [2, 6], [1], [1, 2], [1]]
输出
[null, null, [5], null, null, [6, 5], null, [5]]

解释
Twitter twitter = new Twitter();
twitter.postTweet(1, 5); // 用户 1 发送了一条新推文 (用户 id = 1, 推文 id = 5)
twitter.getNewsFeed(1);  // 用户 1 的获取推文应当返回一个列表,其中包含一个 id 为 5 的推文
twitter.follow(1, 2);    // 用户 1 关注了用户 2
twitter.postTweet(2, 6); // 用户 2 发送了一个新推文 (推文 id = 6)
twitter.getNewsFeed(1);  // 用户 1 的获取推文应当返回一个列表,其中包含两个推文,id 分别为 -> [6, 5] 。推文 id 6 应当在推文 id 5 之前,因为它是在 5 之后发送的
twitter.unfollow(1, 2);  // 用户 1 取消关注了用户 2
twitter.getNewsFeed(1);  // 用户 1 获取推文应当返回一个列表,其中包含一个 id 为 5 的推文。因为用户 1 已经不再关注用户 2

提示:

  • 1 <= userId, followerId, followeeId <= 500
  • 0 <= tweetId <= 104
  • 所有推特的 ID 都互不相同
  • postTweetgetNewsFeedfollowunfollow 方法最多调用 3 * 104
解题思路
1. 每个用户都有自己的推文
2. 一个用户可以关注另一个用户
3. 获取一个用户的推文时会获取自己的和关注的人的
4. 获取的推文按时间先后排序
5. 每次获取的推文的数量为10

其实就是合并k个有序链表,k是动态变化的
完整代码
class maxHeap {
  // 构造函数
  constructor() {
    this.heap = []
  }

  // 交换
  swap(i1, i2) {
    let temp = this.heap[i1]
    this.heap[i1] = this.heap[i2]
    this.heap[i2] = temp
  }

  // 获取父节点

  getParentIndex(index) {
    return (index - 1) >> 1
  }

  getLeftIndex(index) {
    return index * 2 + 1
  }

  getRightIndex(index) {
    return index * 2 + 2
  }

  // 下移操作

  shiftDown(index) {
    // 当前节点往下走
    let left = this.getLeftIndex(index)
    let right = this.getRightIndex(index)

    // 左边和右边都有,跟大的换
    if (this.heap[left] && this.heap[right]) {
      if (
        this.heap[left].time > this.heap[right].time &&
        this.heap[left].time > this.heap[index].time
      ) {
        this.swap(index, left)
        this.shiftDown(left)
      } else if (
        this.heap[left].time <= this.heap[right].time &&
        this.heap[right].time > this.heap[index].time
      ) {
        this.swap(index, right)
        this.shiftDown(right)
      }
    } else {
      if (this.heap[left] && this.heap[left].time > this.heap[index].time) {
        this.swap(index, left)
        this.shiftDown(left)
      }

      if (this.heap[right] && this.heap[right].time > this.heap[index].time) {
        this.swap(index, right)
        this.shiftDown(right)
      }
    }
  }

  // 上移操作

  shiftUp(index) {
    if (index === 0) return

    let parent = this.getParentIndex(index)
    // console.log(this.heap)
    if (this.heap[index].time > this.heap[parent].time) {
      this.swap(index, parent)
      this.shiftUp(parent)
    }
  }

  // 添加元素
  push(node) {
    this.heap.push(node)

    this.shiftUp(this.heap.length - 1)
  }

  // 删除堆顶

  pop() {
    if (this.heap.length) {
      if (this.heap.length === 1) {
        return this.heap.shift()
      }
      let top = this.heap[0]
      this.heap[0] = this.heap.pop()
      this.shiftDown(0)

      return top
    }

    return undefined
  }
}

// 题目的本质其实就是合并k个有序链表,时间序

function Node(val, time) {
  this.val = val
  this.time = time
  this.next = null
}

var Twitter = function () {
  // 用户数组

  // 存放每个用户的链表

  // 存放user -> user的所有推文的映射
  this.Nodemap = new Map()

  // 存放当前user -> 其好友的映射
  this.followMap = new Map()
  this.user = []

  // 下一次的时间
  this.nextTime = 0
}

/**
 * @param {number} userId
 * @param {number} tweetId
 * @return {void}
 */
Twitter.prototype.postTweet = function (userId, tweetId) {
  let node = new Node(tweetId, this.nextTime)
  // console.log(node)
  this.nextTime++
  if (this.Nodemap.has(userId)) {
    node.next = this.Nodemap.get(userId)
  }
  this.Nodemap.set(userId, node)
}

/**
 * @param {number} userId
 * @return {number[]}
 */
Twitter.prototype.getNewsFeed = function (userId) {
  // 看当前用户有没有好友

  // 需要合并的链表
  let arr = [userId]

  if (this.followMap.has(userId)) {
    // console.log(this.followMap.get(userId), 'friend')
    arr = arr.concat(this.followMap.get(userId))
    // arr.push()
  }

  let h = new maxHeap()

  if (arr.length === 1) {
    let res = []
    let p = this.Nodemap.get(userId)
    while (p != null) {
        console.log(p.val)
      res.push(p.val)
      p = p.next
    }
    return res.slice(0,10)
  } else {
    let res = []
    // 进行链表合并
    // 最大堆,时间最大的放在堆顶
    for (let i = 0; i < arr.length; i++) {
      let node = this.Nodemap.get(arr[i])
    //   console.log(node, 'test', arr)
    if(node){
        h.push(node)
    }
      
    }

    while (h.heap.length) {
      // console.log(1)

      let top = h.pop()

      res.push(top.val)

      if (top.next) {
        h.push(top.next)
      }
    }

    return res.slice
  }
}

/**
 * @param {number} followerId
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.follow = function (followerId, followeeId) {
  let users = this.followMap.get(followerId)

  if (users) {
    // // console.log(users)
    if (users.includes(followeeId)) return
    // console.log(users, '111')
    users.push(followeeId)
    this.followMap.set(followerId, users)
  } else {
    let arr = Array.from([])
    arr.push(followeeId)
    this.followMap.set(followerId, arr)

    // console.log(this.followMap.get(followerId), '666')
  }
}

/**
 * @param {number} followerId
 * @param {number} followeeId
 * @return {void}
 */
Twitter.prototype.unfollow = function (followerId, followeeId) {
  let users = this.followMap.get(followerId)
  // console.log(users, '112')
  if (!users) return

  if (!users.includes(followeeId)) return

  let res = users.filter((item) => item !== followeeId)

  this.followMap.set(followerId, res)
}

/**
 * Your Twitter object will be instantiated and called as such:
 * var obj = new Twitter()
 * obj.postTweet(userId,tweetId)
 * var param_2 = obj.getNewsFeed(userId)
 * obj.follow(followerId,followeeId)
 * obj.unfollow(followerId,followeeId)
 */

posted @ 2023-02-04 11:28  凌歆  阅读(26)  评论(0)    收藏  举报