堆是动态求极值的数据结构,所以当遇到需要不断取极值的时候,就可以考虑用堆了
温馨提示,建议每一道题都自己 new 一个堆,这样才能发现堆之美,其实就是不会再次遇到 topK 的时候只能用冒泡来做。
行文至此也该结束了,如果有 10 个关注,那就更新一下下一 part, dp 或者树吧, thx。
class MinHeap {
constructor(len) {
this.data = [];
this.data[0] = len; // 第一个节点用来存放堆的大小 -- 某些特定环境比较好用
}
// 下浮
down(index) {
const size = this.data[0];
while (index << 1 <= size) {
let child = index << 1;
if (child !== size && this.data[child] > this.data[child + 1]) {
child += 1; //如果右节点更小,就右节点作为下一个接盘的节点
}
if (this.data[index] > this.data[child]) {
// 交换一下
[this.data[index], this.data[child]] = [
this.data[child],
this.data[index],
];
index = child;
} else {
// 只要其中一次失效了,那么就没必要继续往下查找了
break;
}
}
}
// 都是从最底层开始算的
upper() {
// 这里不用 this.data[0] 是因为当前构建的堆可能还没达到堆的最大值,所以不能使用
let index = this.data.length - 1;
while (index >> 1 > 0) {
let father = index >> 1;
if (this.data[index] < this.data[father]) {
// 子节点比父节点要小,则网上走
[this.data[index], this.data[father]] = [
this.data[father],
this.data[index],
];
index = father;
} else {
break;
}
}
}
}
堆是为了解决动态取极值
而存在的数据结构,所以必然要取出整理的过程。class MaxHeap {
constructor() {
this.data = [];
this.data[0] = 0; // 第一个值是当前堆的size
}
down(index) {
const len = this.data.length; // 是下标极限
while (index << 1 < len) {
let child = index << 1;
if (child !== len && this.data[child + 1] > this.data[child]) {
child++;
}
if (this.data[child] > this.data[index]) {
[this.data[child], this.data[index]] = [
this.data[index],
this.data[child],
];
index = child;
} else {
break;
}
}
}
upper() {
let index = this.data[0]; // 最大下标
while (index >> 1 > 0) {
const father = index >> 1;
if (this.data[index] > this.data[father]) {
[this.data[father], this.data[index]] = [
this.data[index],
this.data[father],
];
index = father;
} else {
break;
}
}
}
add(value) {
// 先加在最末尾
this.data.push(value);
this.data[0]++; // size 也加一下
this.upper(); // 整理一下
}
// 弹出堆顶元素
pop() {
const last = this.data[0];
[this.data[1], this.data[last]] = [this.data[last], this.data[1]]; //交换堆顶和堆尾的值
const ret = this.data.pop();
this.data[0]--;
this.down(1); // 整理
return ret;
}
}
参考视频:传送门
分析
// 215. 数组中的第K个最大元素
// MinHeap 就是上面的那个类
var findKthLargest = function (nums, k) {
// 创建一个 K 大的小顶堆
const minHeap = new MinHeap(k);
const len = nums.length;
for (let i = 0; i < len; i++) {
if (i < k) {
// 初始化堆
minHeap.data.push(nums[i]);
minHeap.upper();
} else {
// 这个时候开始考虑是否要压到小顶堆中了
// 因为维护的是一个 k 大的小顶堆,而目的是求第 k 大,所以只需要判断后面的值是否大于小顶堆的堆顶值,即可晓得是否需要取代
if (nums[i] > minHeap.data[1]) {
minHeap.data[1] = nums[i];
minHeap.down(1);
}
}
}
return minHeap.data[1]
};
分析 -- 大顶堆
// 1046. 最后一块石头的重量
var lastStoneWeight = function (stones) {
// 维护一个大顶堆
const heap = new MaxHeap();
for (let i = 0; i < stones.length; i++) {
heap.add(stones[i]);
}
while (heap.data[0] > 1) {
// 1. 每次取出两个最大值
const first = heap.pop();
const second = heap.pop();
// 2. 相减,再放回去
const temp = first - second;
if (temp) {
heap.add(temp);
}
}
return heap.data[0] ? heap.data[1]:0
};
class MaxHeap {
constructor() {
this.data = [];
this.data[0] = 0; // 第一个值是当前堆的size
}
down(index) {
const len = this.data.length; // 是下标极限
while (index << 1 < len) {
let child = index << 1;
if (child !== len && this.data[child + 1] > this.data[child]) {
child++;
}
if (this.data[child] > this.data[index]) {
[this.data[child], this.data[index]] = [
this.data[index],
this.data[child],
];
index = child;
} else {
break;
}
}
}
upper() {
let index = this.data[0]; // 最大下标
while (index >> 1 > 0) {
const father = index >> 1;
if (this.data[index] > this.data[father]) {
[this.data[father], this.data[index]] = [
this.data[index],
this.data[father],
];
index = father;
} else {
break;
}
}
}
add(value) {
// 先加在最末尾
this.data.push(value);
this.data[0]++; // size 也加一下
this.upper(); // 整理一下
}
// 弹出堆顶元素
pop() {
const last = this.data[0];
[this.data[1], this.data[last]] = [
this.data[last],
this.data[1],
] //交换堆顶和堆尾的值
const ret = this.data.pop();
this.data[0]--;
this.down(1); // 整理
return ret
}
}
// 23. 合并K个升序链表
/** * @分析 * 1. 以链表的表头值作为判断元素,创建小顶堆 */
var mergeKLists = function(lists) {
let emptyNode = new ListNode() // 自己创建一个
// 构建一个堆
const minHeap = new MinHeap()
for (let i = 0; i < lists.length; i++) {
const head = lists[i];
if(head){
minHeap.add(head)
}
}
let cur = emptyNode;
while(minHeap.data[0]){
cur.next =new ListNode(minHeap.pop())
cur = cur.next
}
return emptyNode.next
};
class MinHeap {
constructor(){
this.data = []
this.data[0] = 0
}
down(index){
const lastIndex = this.data[0]
while(index<<1 <= lastIndex){
let child = index<<1
if(child!== lastIndex && this.data[child+1].val<this.data[child].val){
child++
}
if(this.data[child].val<this.data[index].val){
[this.data[child],this.data[index]] = [this.data[index],this.data[child]]
index = child
}else{
break
}
}
}
upper(){
let index = this.data[0]
while(index>>1 > 0){
let father = index>>1
// 以表头值作为判断依据
if(this.data[father].val>this.data[index].val){
// 交换的是整个链表
[this.data[father],this.data[index]] = [this.data[index],this.data[father]]
index = father
}else{
break
}
}
}
add(head){
// 添加的是一个排好序的链表,
this.data.push(head);
this.data[0]++
this.upper()
}
// 将堆顶链表的头部值取出之后,重新整理
pop(){
const ret = this.data[1].val
this.data[1] = this.data[1].next
if(!this.data[1]){
// 链表为 undefined 了
[this.data[1],this.data[this.data[0]]] = [this.data[this.data[0]],this.data[1]]
this.data.pop()
this.data[0]--
}
this.down(1) //整理
return ret // 返回的是一个值
}
}
/** * @分析 * 1. 多个排序链表很难处理,那么两个排序好的链表合并呢? * 2. 将两个有序链表合成一个,然后放进 list 继续走,最后合成一个即可 * 3. 用分治,如果超出 2 个链表,就拆分了处理,mergeKLists(arr) 最后得到的也是一个排序好的链表,所以每次可以分开治,然后最后 merge 治; * 4. 合并两个链表的时间复杂度 ${O(N)}$,分治处理 M 个链表的复杂度为 ${O(logM)}$ 所以 ${O(NlogM)}$ , 其中 N 是链表的长度, M 是链表的数量 */
var mergeKLists = function(lists) {
const len =lists.length
// return lists.reduce((prev,cur) => mergeTwoList(prev,cur)) // 直接 api 递推即可,但是复杂度更高
// 用分治的方式可以将 N 的复杂度降低到 logN
if(len<1) return null
if(len === 1) return lists[0]
if(len === 2) return mergeTwoList(lists[0],lists[1])
const mid = len>>1
return mergeTwoList(mergeKLists(lists.slice(0,mid)),mergeKLists(lists.slice(mid)))
};
// 合并两个有序链表
function mergeTwoList (list1,list2){
const emptyNode = new ListNode()
let cur =emptyNode
while(list1 && list2){
if(list1.val<list2.val){
cur.next= list1
list1 = list1.next
}else{
cur.next= list2
list2 = list2.next
}
cur = cur.next
cur.next = null
}
if(list1) cur.next = list1
if(list2) cur.next = list2
return emptyNode.next
}
// 451. 根据字符出现频率排序
var frequencySort = function (s) {
let ret = "";
if (!s) return s;
const map = new Map();
const heap = new MaxHeap();
for (let i = 0; i < s.length; i++) {
const item = s[i];
if (map.has(item)) {
map.set(item, map.get(item) + 1);
} else {
map.set(item, 1);
}
}
// 加入堆中,元素值是 [key,value], 要用 value 来进行比对
for (let item of map.entries()) {
heap.add(item);
}
while (heap.data[0]) {
const item = heap.pop();
ret += item[0].repeat(item[1]);
}
return ret;
};
class MaxHeap {
constructor() {
this.data = [];
this.data[0] = 0;
}
down(index) {
const lastIndex = this.data[0]; //最后一个值的下标值
while (index << 1 <= lastIndex) {
let child = index << 1;
if (
child !== lastIndex &&
this.data[child + 1][1] > this.data[child][1]
) {
child++;
}
if (this.data[child][1] > this.data[index][1]) {
// 注意,item 是数组,所以用第二个值做比对,但是交换的是整个 item
[this.data[child], this.data[index]] = [
this.data[index],
this.data[child],
];
index = child;
} else {
break;
}
}
}
upper() {
let index = this.data[0];
while (index >> 1 > 0) {
const father = index >> 1;
if (this.data[father][1] < this.data[index][1]) {
// 注意,item 是数组,所以用第二个值做比对,但是交换的是整个 item
[this.data[father], this.data[index]] = [
this.data[index],
this.data[father],
];
index = father;
} else {
break;
}
}
}
add(item) {
this.data.push(item);
this.data[0]++;
this.upper();
}
pop() {
[this.data[1], this.data[this.data[0]]] = [
this.data[this.data[0]],
this.data[1],
];
this.data[0]--;
const temp = this.data.pop();
this.down(1)
return temp
}
}
// 378. 有序矩阵中第 K 小的元素
/** * @分析 -- 第 K 小 * 1. 这里给的排好序的矩阵,那么可以用小顶堆将矩阵中元素转移到小顶堆中,每次从堆顶取值后整理,取到第 K 个即可 */
var kthSmallest = function(matrix, k) {
const minHeap = new MinHeap()
for(let i = 0;i<matrix.length;i++){
minHeap.add(matrix[i])
}
const ret = []
while(--k){
minHeap.pop()
}
return minHeap.pop()
};
class MinHeap {
constructor(){
this.data = []
this.data[0] = 0
}
down(index){
const lastIndex = this.data[0]
while(index<<1 <= lastIndex){
let child = index << 1
if(child!==lastIndex && this.data[child+1][0]< this.data[child][0]){
child++
}
if(this.data[child][0]< this.data[index][0]){
[this.data[child], this.data[index]] = [this.data[index], this.data[child]]
index = child
}else {
break
}
}
}
upper() {
let index = this.data[0]
while(index >>1 > 0){
let father = index >> 1
if(this.data[father][0]> this.data[index][0]){
[this.data[father], this.data[index]] = [this.data[index], this.data[father]]
index = father
}else {
break
}
}
}
add(item){
this.data.push(item)
this.data[0]++
this.upper()
}
// 这里不是直接弹出 item,而只是弹出堆顶的第一个字母,然后再整理
pop(){
const temp = this.data[1].shift()
if(!this.data[1].length){
// 数组空了
this.data[1] = this.data[this.data[0]]
this.data.pop()
this.data[0]--
}
this.down(1)
return temp
}
}
const ret = kthSmallest([[1,5,9],[10,11,13],[12,13,15]],8)
console.log(ret)
// 1054. 距离相等的条形码
var rearrangeBarcodes = function (barcodes) {
// 1. 将条形码的值和数量用 map 存储起来
const map = new Map();
for (let i = 0; i < barcodes.length; i++) {
const item = barcodes[i];
if (map.has(item)) {
map.set(item, map.get(item) + 1);
} else {
map.set(item, 1);
}
}
// 2.创建最大堆,进行堆排
const heap = new MaxHeap();
for (let item of map.entries()) {
heap.add(item); // [key,value]
}
// 3. 每次取出最大的两个 item 进行重写排列
const ret = [];
while (heap.data[0] > 1) {
// 这里是默认是保证存在答案,所以即便最后还有item,那么对应的值也只有1个
// 但是如果条件没有已知,那么就可以根据这个值进行判断是否成功了
const first = heap.pop();
const second = heap.pop();
// Error:错误
// while(second[1]--){
// ret.push(first[0])
// ret.push(second[0])
// first[1]--
// }
ret.push(first[0]);
first[1]--;
ret.push(second[0]);
second[1]--;
// 然后就要放回去了
if (first[1]) {
// 如果还有值,放回堆里再来
heap.add(first);
}
if (second[1]) {
// 如果还有值,放回堆里再来
heap.add(second);
}
}
if (heap.data[0]) {
ret.push(heap.pop()[0]);
}
return ret;
};
class MaxHeap {
constructor() {
this.data = [];
this.data[0] = 0;
}
down(index) {
const lastIndex = this.data[0];
while (index << 1 <= lastIndex) {
let child = index << 1;
if (
child !== lastIndex &&
this.data[child + 1][1] > this.data[child][1]
) {
child++;
}
if (this.data[child][1] > this.data[index][1]) {
[this.data[child], this.data[index]] = [
this.data[index],
this.data[child],
];
index = child;
} else {
break;
}
}
}
upper() {
let index = this.data[0];
while (index >> 1 > 0) {
let father = index >> 1;
if (this.data[father][1] < this.data[index][1]) {
[this.data[father], this.data[index]] = [
this.data[index],
this.data[father],
];
index = father;
} else {
break;
}
}
}
add(item) {
this.data.push(item);
this.data[0]++;
this.upper();
}
pop() {
[this.data[1], this.data[this.data[0]]] = [
this.data[this.data[0]],
this.data[1],
];
const item = this.data.pop();
this.data[0]--;
this.down(1);
return item;
}
}
console.log(rearrangeBarcodes([7, 7, 7, 8, 5, 7, 5, 5, 5, 8]));
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。