堆-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,它有两个邻居:节点 24
节点 2 的值是 2,它有两个邻居:节点 13
节点 3 的值是 3,它有两个邻居:节点 24
节点 4 的值是 4,它有两个邻居:节点 13

示例 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 @   凌歆  阅读(14)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!
点击右上角即可分享
微信分享提示