堆专题(上)
堆系列问题的特点
注:本文章系本人阅读博主lucifer的leetcode题解做的总结,涉及到的题目均来自leetcode官网,侵删。
1.1 中心:动态求极值
堆一般用于求极值问题,但如果不是动态的就没必要使用堆,会增加时间成本。
所谓动态,简单来说就是堆内数据在动态变化,也就是堆的大小在变化,下面举例说明。
例一、1046.最后一块石头的重量
题目描述:
有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块 最重的 石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。思路:
每次选择最重的两块石头进行粉碎之后,最重的石头的重量便发生了改变,将会影响到下次取最重的石头,简单来说就是最重的石头在模拟过程中是动态变化的。
代码:
public int lastStoneWeight(int[] stones) { int n = stones.length; PriorityQueue<Integer> maxHeap = new PriorityQueue<>(n, (a, b) -> b - a); for (int stone : stones) { maxHeap.add(stone); } while (maxHeap.size() >= 2) { Integer head1 = maxHeap.poll(); Integer head2 = maxHeap.poll(); if (head1==head2) { continue; } maxHeap.offer(head1 - head2); } if (maxHeap.isEmpty()) { return 0; } return maxHeap.poll(); }
例二、313. 超级丑数
题目描述:
超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。
给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。
题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。
思路:
对于丑数的定义,有以下结论:
- 1是最小的丑数
- 对于任意一个丑数x,其与任意给定的质因数primes[i]相乘,结果仍为丑数。
解题步骤:
- 起始先将最小丑数 1 放入队列
- 每次从队列取出最小值 x,然后将 x 所对应的丑数 x * primes[i]进行入队。
- 对步骤 2 循环多次,第 n 次出队的值即是答案。
代码:
public int nthSuperUglyNumber(int n, int[] primes){ PriorityQueue<Integer> queue=new PriorityQueue<>(); queue.add(1); while(n-- > 0) { int x = queue.poll(); if (n == 0) return x; for(int k : primes) { if (k <= Integer.MAX_VALUE / x) queue.add(k * x); if (x % k == 0) break; } } return -1; }
1.2 实现:二叉堆
以小顶堆为例,实现堆的功能,关键是始终维持堆的性质不变,也就是父节点的权值不大于子节点的权值。
为了达到这个目的,我们需要在出堆和入堆的时候,使用上浮和下沉操作,并恰当地完成元素交换。具体来说就是上浮过程和比它大的父节点进行交换,下沉过程和两个子节点中较小的进行交换,当然前提是它有两个子节点且子结点比它小。
关键代码:
/** * 前置条件:起点为1 * 核心方法: * shiftDown: 交换下沉 * shiftUp: 交换上浮 * build: 构建堆 **/ public void shiftDown(int i) { int temp = queue[i]; while ((i << 1) <= size) { int child = i << 1; if (child != size && queue[child+1] < queue[child]) { child++; } if (temp > queue[child]) { queue[i] = queue[child]; i = child; } else { break; } } queue[i] = temp; } public void shiftUp(int i) { int temp = queue[i]; while ((i >> 1)>0) { if (temp < queue[i >> 1]) { queue[i] = queue[i >> 1]; i >>= 1; } else { break; } } queue[i] = temp; } public void buildHeap() { for (int i = size >> 1; i > 0; i--) { shiftDown(i); } }