堆专题(上)

堆专题(上)

堆系列问题的特点

注:本文章系本人阅读博主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. 起始先将最小丑数 1 放入队列
  2. 每次从队列取出最小值 x,然后将 x 所对应的丑数 x * primes[i]进行入队。
  3. 对步骤 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);
        }
    }

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:深蓝海洋 设计师:CSDN官方博客 返回首页

打赏作者

长安不识三七

你的鼓励将是我创作的最大动力

¥2 ¥4 ¥6 ¥10 ¥20
输入1-500的整数
余额支付 (余额:-- )
扫码支付
扫码支付:¥2
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值