堆-cnblog

LeetCode

133. 克隆图

给你无向 连通 图中一个节点的引用,请你返回该图的 深拷贝(克隆)。

图中的每个节点都包含它的值 valint) 和其邻居的列表(list[Node])。

class Node {
    public int val;
    public List<Node> neighbors;
}

测试用例格式:

简单起见,每个节点的值都和它的索引相同。例如,第一个节点值为 1(val = 1),第二个节点值为 2(val = 2),以此类推。该图在测试用例中使用邻接列表表示。

邻接列表 是用于表示有限图的无序列表的集合。每个列表都描述了图中节点的邻居集。

给定节点将始终是图中的第一个节点(值为 1)。你必须将 给定节点的拷贝 作为对克隆图的引用返回。

示例 1:

img

输入:adjList = [[2,4],[1,3],[2,4],[1,3]]
输出:[[2,4],[1,3],[2,4],[1,3]]
解释:
图中有 4 个节点。
节点 1 的值是 1,它有两个邻居:节点 2 和 4 。
节点 2 的值是 2,它有两个邻居:节点 1 和 3 。
节点 3 的值是 3,它有两个邻居:节点 2 和 4 。
节点 4 的值是 4,它有两个邻居:节点 1 和 3 。

示例 2:

img

输入:adjList = [[]]
输出:[[]]
解释:输入包含一个空列表。该图仅仅只有一个值为 1 的节点,它没有任何邻居。

示例 3:

输入:adjList = []
输出:[]
解释:这个图是空的,它不含任何节点。

示例 4:

img

输入:adjList = [[2],[1]]
输出:[[2],[1]]

提示:

  1. 节点数不超过 100 。
  2. 每个节点值 Node.val 都是唯一的,1 <= Node.val <= 100
  3. 无向图是一个简单图,这意味着图中没有重复的边,也没有自环。
  4. 由于图是无向的,如果节点 p 是节点 q 的邻居,那么节点 q 也必须是节点 p 的邻居。
  5. 图是连通图,你可以从给定节点访问到所有节点。
解题思路
1. 深度优先搜索或者广度优先搜索
2. 克隆每一个访问的节点
3. 克隆每一个访问的节点所有的边
完整代码
/**
 * // Definition for a Node.
 * function Node(val, neighbors) {
 *    this.val = val === undefined ? 0 : val;
 *    this.neighbors = neighbors === undefined ? [] : neighbors;
 * };
 */

/**
 * @param {Node} node
 * @return {Node}
 */
var cloneGraph = function (node) {
  // console.log(node.neighbors)
  // console.log(node.neighbors[0].neighbors)

  // 无向图的搜索

  // 每次创建新的节点
  // console.log(node)

  if (!node) {
    return null
  }

  if (node.length === 1 && node.val === 1) {
    // console.log(1)
    return new Node(1, [])
  }

  // 访问数组集合  
  const visited = new Set()

  
  // 先来一次dfs,将新节点创建出来
  // 克隆节点的数组
  let nodeList = []
  const nodeNew = new Node(node.val, [])
  // res.neighbors.push(nodeNew)
  nodeList.push(nodeNew)

  const dfs = root => {
    // console.log(root.val)
    // 加入访问数组  
    visited.add(root.val)

    root.neighbors.forEach(node => {
      if (!visited.has(node.val)) {
        // 克隆邻居节点  
        const nodeNew = new Node(node.val, [])
        // res.neighbors.push(nodeNew)
        nodeList[node.val - 1] = nodeNew
        dfs(node)
      }
	  // 克隆邻居节点所有的边
      nodeList[root.val - 1].neighbors.push(nodeList[node.val - 1])
    })
  }

  dfs(node)

  console.log(nodeList)

  console.log(nodeList[0])
  console.log(nodeList[1])
  console.log(nodeList[2])
  console.log(nodeList[3])

  // const res=nodeList[0]

  return nodeList[0]
}

优化(广度优先搜索+Map)
/**
 * // Definition for a Node.
 * function Node(val, neighbors) {
 *    this.val = val === undefined ? 0 : val;
 *    this.neighbors = neighbors === undefined ? [] : neighbors;
 * };
 */

/**
 * @param {Node} node
 * @return {Node}
 */
var cloneGraph = function (node) {
  if (!node) {
    return null
  }

  if (node.length === 1 && node.val === 1) {
    return new Node(1, [])
  }

  // 建立源节点(node,nodeNew)之间的映射关系
  const visited = new Map()
  const nodeNew = new Node(node.val, [])
  visited.set(node, nodeNew)

  let queue = []
  queue.push(node)

  while (queue.length) {
    const node = queue.shift()

    node.neighbors.forEach(nextNode => {
      if (!visited.has(nextNode)) {
        queue.push(nextNode)
        // 设置映射关系 克隆节点
        visited.set(nextNode, new Node(nextNode.val, []))
      }

      // 克隆边

      // 获取外层node 对应的 克隆node visited.get(node)
      // 获取其对应邻居的克隆节点
      visited.get(node).neighbors.push(visited.get(nextNode))
    })
  }

  return nodeNew
  // return nodeList[0]
}

  • 特殊的完全二叉树
  • 完全二叉树
    • 一棵深度为k的有n个结点的二叉树,对树中的结点按从上至下、从左到右的顺序进行编号,如果编号为i(1≤i≤n)的结点与满二叉树中编号为i的结点在二叉树中的位置相同,则这棵二叉树称为完全二叉树
  • 堆要么是最大堆,要么是最小堆
  • 最大堆
    • 所有节点都大于它的子节点
  • 最小堆
    • 所有节点都小于它的子节点

js中表示堆

因为js中没有提供堆直接实现的数据结构

代码实现一个最大堆(最小堆也差不多)

class MaxHeap {
  constructor() {
    this.heap = []
  }

  // 获取堆的大小
  size() {
    return this.heap.length
  }

  // 获取堆顶
  peek() {
    // 为什么不加判断,因为不存在就是undefined
    return this.heap[0]
  }

  // 获取当前节点的左节点下标
  getLeftIndex(index) {
    return index * 2 + 1
  }

  // 获取当前节点的右节点的下标
  getRightIndex(index) {
    return index * 2 + 2
  }

  // 获取当前节点的父节点下标
  getParentIndex(index) {
    return Math.floor(index / 2)
  }

  // 交换节点
  swap(index1, index2) {
    let temp = this.heap[index1]
    this.heap[index1] = this.heap[index2]
    this.heap[index2] = temp
  }

  // 上移操作
  shiftUp(index) {
    if (index === 0) return
    const parentIndex = this.getParentIndex(index)

    // 判断当前节点值是否大于父节点的值
    if (this.heap[index] > this.heap[parentIndex]) {
      // 上移,即交换
      this.swap(index, parentIndex)

      // 继续上移,直到不满足大于条件,或者parentIndex=0
      this.shiftUp(parentIndex)
    }
  }

  // 向堆中插入一个新的节点
  insert(value) {
    // 默认插入数组的尾部
    this.heap.push(value)
    // 然后开始上移,如果改节点大于它的父节点,依此类推
    this.shiftUp(this.size() - 1)
  }

  // 下移节点,如果当前节点小于子节点
  shiftDown(index) {
    const leftIndex = this.getLeftIndex(index)
    const rightIndex = this.getRightIndex(index)

    if (this.heap[index] < this.heap[leftIndex]) {
      this.swap(index, leftIndex)
      this.shiftDown(leftIndex)
    }

    if (this.heap[index] < this.heap[rightIndex]) {
      this.swap(index, rightIndex)
      this.shiftDown(rightIndex)
    }
  }

  // 移除堆顶元素
  pop() {
    //为了不破坏原有的结构
    this.heap[0] = this.heap.pop()
    this.shiftDown(0)
    // 将最后一个在下移一下
  }
}

const heap = new MaxHeap()

heap.insert(3)
heap.insert(5)
heap.insert(8)

heap.peek()

heap.pop()

heap.peek()

堆-LeetCode

215. 数组中的第K个最大元素

给定整数数组 nums 和整数 k,请返回数组中第 **k** 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

你必须设计并实现时间复杂度为 O(n) 的算法解决此问题。

示例 1:

输入: [3,2,1,5,6,4], k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6], k = 4
输出: 4

提示:

  • 1 <= k <= nums.length <= 105
  • -104 <= nums[i] <= 104
解题思路
1. 使用刚学的数据结构-堆
2. 最小堆,因为堆顶元素一直是最小的
3. 但堆的大小达到最大时,每次新的元素添加,必有旧的元素离开(最小的)
4. 那么最后留下了的即是最大的k个数(k为堆长度)
代码实现
  • 最小堆堆数据结构
class MinHeap {
  constructor() {
    this.heap = []
  }
  swap(i1, i2) {
    const temp = this.heap[i1]
    this.heap[i1] = this.heap[i2]
    this.heap[i2] = temp
  }
  getParentIndex(i) {
    // (i-1)/2
    return (i - 1) >> 1
  }
  getLeftIndex(i) {
    return i * 2 + 1
  }
  getRightIndex(i) {
    return i * 2 + 2
  }
  shiftUp(index) {
    if (index == 0) {
      return
    }
    const parentIndex = this.getParentIndex(index)
    if (this.heap[parentIndex] > this.heap[index]) {
      this.swap(parentIndex, index)
      this.shiftUp(parentIndex)
    }
  }
  shiftDown(index) {
    const leftIndex = this.getLeftIndex(index)
    const rightIndex = this.getRightIndex(index)
    if (this.heap[leftIndex] < this.heap[index]) {
      this.swap(leftIndex, index)
      this.shiftDown(leftIndex)
    }
    if (this.heap[rightIndex] < this.heap[index]) {
      this.swap(rightIndex, index)
      this.shiftDown(rightIndex)
    }
  }
  insert(value) {
    this.heap.push(value)
    this.shiftUp(this.heap.length - 1)
  }
  pop() {
    this.heap[0] = this.heap.pop()
    this.shiftDown(0)
  }
  peek() {
    return this.heap[0]
  }
  size() {
    return this.heap.length
  }
}

const h = new MinHeap()
h.insert(3)
h.insert(2)
h.insert(1)
h.pop()

  • 主函数
var findKthLargest = function (nums, k) {
  // 使用最小堆

  // 最小堆的长度固定是,每超过一个,就会移除栈顶(最小的) ,也就是每次超出,都会移除一个最小值(那保留的就是k个最大值)

  const heap = new MinHeap()

  /*
  * 超时了,
  * 算法是正确的
  */
  // nums.forEach((item,i)=>{
  //     console.log(item,i)

  //         heap.insert(item)

  //         if(heap.size()>k){

  //             heap.pop()

  //         }

  // })

  // 优化版本=> insert之前加上判断
  // 减少不必要的push操作
  for (let i = 0; i < nums.length; i++) {
    if (i < k) {
      heap.insert(nums[i])
    } else {
      if (nums[i] >= heap.heap[0]) {
        heap.heap[0] = nums[i]
        heap.shiftDown(0)
      }
    }
  }

  // 循环后,栈顶就是第k个最大值(size长度为k,保留了k个最大的)

  console.log(heap.heap[0])
  return heap.peek()
}

347. 前 K 个高频元素

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

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

示例 2:

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

提示:

  • 1 <= nums.length <= 105
  • k 的取值范围是 [1, 数组中不相同的元素的个数]
  • 题目数据保证答案唯一,换句话说,数组中前 k 个高频元素的集合是唯一的

进阶:你所设计算法的时间复杂度 必须 优于 O(n log n) ,其中 n 是数组大小。

解题思路
和上面一个题目一样
/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number[]}
 */

class MinHeap {
  constructor() {
    this.heap = []
  }
  swap(i1, i2) {
    const temp = this.heap[i1]
    this.heap[i1] = this.heap[i2]
    this.heap[i2] = temp
  }
  getParentIndex(i) {
    // (i-1)/2
    return (i - 1) >> 1
  }
  getLeftIndex(i) {
    return i * 2 + 1
  }
  getRightIndex(i) {
    return i * 2 + 2
  }
  shiftUp(index) {
    if (index == 0) {
      return
    }
    const parentIndex = this.getParentIndex(index)
    if (this.heap[parentIndex]&&this.heap[parentIndex].value > this.heap[index].value) {
      this.swap(parentIndex, index)
      this.shiftUp(parentIndex)
    }
  }
  shiftDown(index) {
    const leftIndex = this.getLeftIndex(index)
    const rightIndex = this.getRightIndex(index)
    if (this.heap[leftIndex]&&this.heap[leftIndex].value < this.heap[index].value) {
      this.swap(leftIndex, index)
      this.shiftDown(leftIndex)
    }
    if (this.heap[rightIndex]&&this.heap[rightIndex].value < this.heap[index].value) {
      this.swap(rightIndex, index)
      this.shiftDown(rightIndex)
    }
  }
  insert(value) {
    this.heap.push(value)
    this.shiftUp(this.heap.length - 1)
  }
  pop() {
    this.heap[0] = this.heap.pop()
    this.shiftDown(0)
  }
  peek() {
    return this.heap[0]
  }
  size() {
    return this.heap.length
  }
}

const h = new MinHeap()
h.insert(3)
h.insert(2)
h.insert(1)
h.pop()

 
// 1.堆排
// 2.快排
var topKFrequent = function(nums, k) {

    const m=new Map()
    // 对出现次数进行统计
    nums.forEach((item)=>{
        m.set(item,m.has(item)? m.get(item)+1:1)
    })

    // 这里可以使用快速排序(Olog(N))或者堆排

    // 但是堆排时间复杂度 (O log(K))k<=n
    let p=new MinHeap()
    m.forEach((value,key)=>{
        p.insert({value,key})

        if(p.size()>k){
            p.pop()
        }

    })

    return p.heap.map(item=>item.key)
    

};

23. 合并K个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]]
输出:[1,1,2,3,4,4,5,6]
解释:链表数组如下:
[
  1->4->5,
  1->3->4,
  2->6
]
将它们合并到一个有序链表中得到。
1->1->2->3->4->4->5->6

示例 2:

输入:lists = []
输出:[]

示例 3:

输入:lists = [[]]
输出:[]

提示:

  • k == lists.length
  • 0 <= k <= 10^4
  • 0 <= lists[i].length <= 500
  • -10^4 <= lists[i][j] <= 10^4
  • lists[i]升序 排列
  • lists[i].length 的总和不超过 10^4
解题思路
1. 堆排序,每次输出最小值
2. 把各个链表头进堆
3. 每次出堆的为最小值,同时把它的后继节点入堆
4. 注意点
	当只剩下一个节点时
	pop() {
    // 这里需要修改
    // 每次pop需要返回值
    
    if(this.size()===1) return this.heap.shift()
    const top=this.heap[0]
    this.heap[0] = this.heap.pop()
    this.shiftDown(0)
    return top;
  }
完整代码
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
class MinHeap {
  constructor() {
    this.heap = []
  }
  swap(i1, i2) {
    const temp = this.heap[i1]
    this.heap[i1] = this.heap[i2]
    this.heap[i2] = temp
  }
  getParentIndex(i) {
    // (i-1)/2
    return (i - 1) >> 1
  }
  getLeftIndex(i) {
    return i * 2 + 1
  }
  getRightIndex(i) {
    return i * 2 + 2
  }
  shiftUp(index) {
    if (index == 0) {
      return
    }
    const parentIndex = this.getParentIndex(index)
    if (
      this.heap[parentIndex] &&
      this.heap[parentIndex].val > this.heap[index].val
    ) {
      this.swap(parentIndex, index)
      this.shiftUp(parentIndex)
    }
  }
  shiftDown(index) {
    const leftIndex = this.getLeftIndex(index)
    const rightIndex = this.getRightIndex(index)
    if (
      this.heap[leftIndex] &&
      this.heap[leftIndex].val < this.heap[index].val
    ) {
      this.swap(leftIndex, index)
      this.shiftDown(leftIndex)
    }
    if (
      this.heap[rightIndex] &&
      this.heap[rightIndex].val < this.heap[index].val
    ) {
      this.swap(rightIndex, index)
      this.shiftDown(rightIndex)
    }
  }
  insert(value) {
    this.heap.push(value)
    this.shiftUp(this.heap.length - 1)
  }
  pop() {
    // 这里需要修改
    // 每次pop需要返回值
    if (this.size() === 1) return this.heap.shift()
    const top = this.heap[0]
    this.heap[0] = this.heap.pop()
    this.shiftDown(0)
    return top
  }
  peek() {
    return this.heap[0]
  }
  size() {
    return this.heap.length
  }
}

// 使用堆排序
// 因为只需要每次输出最小值出来,最下堆
var mergeKLists = function (lists) {
  let h = new MinHeap()
  lists.forEach(node => {
    if (node) {
      h.insert(node)
    }
  })

  const res = new ListNode(-1)

  let res1 = res

  while (h.size()) {
    // 最小的弹出
    const top = h.pop()
    console.log(top)
    // res1.next=new ListNode(top.val)
    res1.next = top
    res1 = res1.next
    // 把后面的节点进堆
    if (top.next) {
      h.insert(top.next)
    }
  }

  return res.next
}

posted @ 2023-02-05 10:10  凌歆  阅读(57)  评论(0)    收藏  举报