算法03-堆


前言


下面是堆

一、堆的概念以及java内部的堆的实现

  • 堆的概念是:1、是完全二叉树。2、根节点都大于或者小于左右子节点。
  • 在java中的堆的实现通过内置的任务队列类进行具体如下:
//这个默认为小顶堆
PriorityQueue<Integer> queue = new PriorityQueue<>();
//这个默认为大顶堆
PriorityQueue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
 });

关于堆的一些算法题

二、有关堆的算法题解

2.1 数组中的第K大个元素–力扣215题

  • 题目要求

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

  • 题解思路

给定一个数组以及一个K值,获取这个数组的第k大个元素。那么这个题目不难思考,可以使用小顶堆的做,为什么呢?我们都知道小顶堆是最小的一个元素位于对顶,那么对于我们需要取多少个数,那么就是可以理解为我们将前k个大的数都存进去了,因为每次堆顶的元素都是这个堆中的最小的元素,那么我们可以先存储K个数(也就是先定义整个堆的长度)如果存储的数大于k个我们就弹出堆顶的数,每次比较堆顶元素小于需要存进去的那个数就可以直接将堆顶元素弹出,将新的数存储,这样循环遍历整个数组就能在这个小顶堆中存储K个元素,并且堆顶元素就是第K个大的数。

  • 代码实现
class Solution {
    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> queue = new PriorityQueue<>();
        for (int num : nums) {
            if (queue.size()<k || queue.peek()<num){
                queue.offer(num);
                if (queue.size()>k) queue.poll();
            }
        }
        return queue.peek();
    }
}

2.2 数据流中的第K大元素–力扣703题

  • 题目要求

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。 KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。 int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。

  • 题解思路

根据上面的题目要求,可以知道是找到一个数据流中的第K大元素,这个地方同样使用小顶堆的方式进行存储,声明一个小顶堆,在add()方法中编写向小顶堆中添加元素的方法,在构造函数中初始化添加的数组(数据流)以及第K个值(可以理解为整个堆的大小),这样就能够实现获取第K个大的数,方法同上。

  • 代码实现
class KthLargest {
    PriorityQueue<Integer> priorityQueue;
    int k;
    public KthLargest(int k, int[] nums) {
        priorityQueue = new PriorityQueue<>();
        this.k = k;
        for (int num : nums) {
            add(num);
        }
    }
    public int add(int val) {
        priorityQueue.offer(val);
        if (priorityQueue.size()>k){
            priorityQueue.poll();
        }
        return priorityQueue.peek();
    }
}

2.3 最后一块石头的重量-力扣1046题

  • 题目要求

有一堆石头,每块石头的重量都是正整数。
每一回合,从中选出两块最重的石头,然后将它们一起粉碎。假设石头的重量分别为 x 和 y,且 x <= y。那么粉碎的可能结果如下:
如果 x == y,那么两块石头都会被完全粉碎;
如果 x != y,那么重量为 x 的石头将会完全粉碎,而重量为 y 的石头新重量为 y-x。
最后,最多只会剩下一块石头。返回此石头的重量。如果没有石头剩下,就返回 0。

  • 题解思路

题目要求是选出最重的石头做粉碎,重量相减,然后如果有剩余就放入当石头堆中继续找最重的两块石头做粉碎,直到最后有一块石头就直接返回石头重量,没有石头就返回0。这个题就很明显使用大顶堆来做,将最大的石头放在堆顶,每次弹出两块进行比较,相减为0则继续进行,不为零则将相减得到的重量进行offer(存储)直到只有一块或者为空返回。

  • 代码实现
class Solution {
    public int lastStoneWeight(int[] stones) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (int stone : stones) {
            priorityQueue.offer(stone);
        }
        while (priorityQueue.size()>1){
            int one = priorityQueue.poll();
            int two = priorityQueue.poll();
            int pl = one-two;
            if (pl!=0){
               priorityQueue.offer(pl);
            }
        }
        return priorityQueue.isEmpty()?0:priorityQueue.poll();
    }
}

2.4 查找最小的K对数字-力扣373题

  • 题目要求

给定两个以升序排列的整数数组 nums1 和 nums2 , 以及一个整数 k 。
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2 。
请找到和最小的 k 个数对 (u1,v1), (u2,v2) … (uk,vk) 。

  • 题解思路

给定两个数组找到最小的k对数对,在这里方法类似于上面。就是需要两个数组的嵌套循环遍历,将所有的结果都使用一遍,同样声明一个只有K大小的一个小顶堆。在声明这个小顶堆的时候就比较有讲究,每个堆中存储三个元素,数组一的一个u1与数组二的u2,还有就是两个数的和。在声明比较的小顶堆的条件是以两个数的和做比较。

  • 代码实现
class Solution {
    public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        PriorityQueue<int[]> priorityQueue = new PriorityQueue<>(new Comparator<int[]>() {
            @Override
            public int compare(int[] o1, int[] o2) {
                return o2[2] - o1[2];
            }
        });
        for (int i = 0; i < nums1.length; i++) {
            for (int j = 0; j < nums2.length; j++) {
                if (priorityQueue.size()<k || nums1[i]+nums2[j]<priorityQueue.peek()[2]){
                    priorityQueue.offer(new int[]{nums1[i],nums2[j],nums1[i]+nums2[j]});
                    if (priorityQueue.size()>k)  priorityQueue.poll();
                }else break;
            }
        }
        List<List<Integer>> res = new ArrayList<>();
        while (!priorityQueue.isEmpty()){
            int[] ints = priorityQueue.poll();
            res.add(new ArrayList<Integer>(){
                {
                    this.add(ints[0]);
                    this.add(ints[1]);
                }
            });
        }
        return res;

    }
}

2.5 最小的k个数–力扣-剑指offer 40题

  • 题目要求

输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4。

  • 题解思路

这题就比较简单了,应该是在上面的第一题,方法同理,声明一个小顶堆,大小是K,然后遍历存储,大于堆顶元素的将原来的堆顶元素弹出,在将大于的这个元素存储。遍历完成返回这个小顶堆就可以了。

  • 代码实现
class Solution {
    public int[] getLeastNumbers(int[] arr, int k) {
        PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        for (int i : arr) {
            priorityQueue.offer(i);
            if (priorityQueue.size()>k){
                priorityQueue.poll();
            }
        }
        return priorityQueue.stream().mapToInt(i->i).toArray();
    }
}

2.6 数据流中的中位数–力扣295题 与 面试题17.20连续中值

  • 题目要求

中位数是有序列表中间的数。如果列表长度是偶数,中位数则是中间两个数的平均值。

  • 题解思路

如果使用的是一个有序数组存储的话会不会比较简单的获取中位数,当数量为偶数时返回中间两个数的平均值,当为奇数时,返回最中间的那个数即可。但是这里使用的是堆存储,有小顶堆存储的是中位数后面半截的数据,一个大顶堆存储的是中位数据前半截的数据。在数据的存储分配是需要进行处理,保证小顶堆的最小值比大顶堆的最小值小。这样当这个数据流的长度为偶数时就是大顶堆的堆顶元素和小顶堆的堆顶元素的平均值,为奇数时两个堆中的元素数量相减得到是位于前半段还是后半段。

  • 代码实现
class MedianFinder {
    PriorityQueue<Integer> l,r;
    /** initialize your data structure here. */
    public MedianFinder() {
        l = new PriorityQueue<>(new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return o2-o1;
            }
        });
        r = new PriorityQueue<>();
    }
    public void addNum(int num) {
        if (l.isEmpty() || l.peek()>num) l.offer(num);
        else r.offer(num);
        if (r.size()>l.size()){
            l.offer(r.poll());
        }
        if (l.size() == r.size()+2){
            r.offer(l.poll());
        }
    }
    public double findMedian() {
        int n = l.size()+r.size();
        if (n%2!=0) return l.peek();
        return (l.peek()+r.peek())/2.0;
    }
}

2.7 前k个高频单词–力扣692题

  • 题目要求

给一非空的单词列表,返回前 k 个出现次数最多的单词。 返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率,按字母顺序排序。

  • 题解思路

这个题的思路与前面的题的思路是差不多的,重点在于对相同单词的处理,在这个题中,我们先对整个单词列表进行存储(相同的得到数量)。我们通过Map集合的方式,将单词作为key,出现频率作为value。通过map集合的内置函数方法getOrDefault()将相同的Key进行统计叠加。然后同上面的获取前K个出现的单词,一样的创建一个小顶堆进行相关的操作。

  • 代码实现
class Solution {
    public List<String> topKFrequent(String[] words, int k) {
        Map<String,Integer> map = new HashMap<>();
        for (String word : words) {
            map.put(word,map.getOrDefault(word,0)+1);
        }
        PriorityQueue<Map.Entry<String,Integer>> priorityQueue = new PriorityQueue<>(new Comparator<Map.Entry<String, Integer>>() {
            @Override
            public int compare(Map.Entry<String, Integer> o1, Map.Entry<String, Integer> o2) {
                return o1.getValue() == o2.getValue() ? o2.getKey().compareTo(o1.getKey()):o1.getValue()-o2.getValue();
            }
        });
        for (Map.Entry<String, Integer> entry : map.entrySet()) {
            priorityQueue.offer(entry);
            if (priorityQueue.size()>k) priorityQueue.poll();
        }
        List<String> res = new ArrayList<>();
        while (!priorityQueue.isEmpty()){
            String key = priorityQueue.poll().getKey();
            res.add(0,key);
        }
        return res;
    }
}

2.8 移除石子的最大得分–力扣1753题

  • 题目要求

你正在玩一个单人游戏,面前放置着大小分别为 a、b 和 c 的 三堆 石子。
每回合你都要从两个 不同的非空堆 中取出一颗石子,并在得分上加 1 分。当存在 两个或更多 的空堆时,游戏停止。
给你三个整数 a 、b 和 c ,返回可以得到的 最大分数 。

  • 题解思路

实际上很简单,最大石子相减,得到的次数就是最大石子的最优,再将剩余的与开始的石子项比较得到小的那个,再加上最开始两堆石子的结果就可以得到相对应的值。

  • 代码实现
class Solution {
    public int maximumScore(int a, int b, int c) {
      if (a>b){
          int t = a;
          a = b;
          b = t;
      }
      if (a>c){
          int t = a;
          a = c;
          c = t;
      }
      if (b>c){
          int t = b;
          b = c;
          c = t;
      }if (a<=c-b) return a+b;
      return (a+b+c)/2;
    }
}

总结

这个是堆的一些算法,自己的题解思路记录。同时感觉自己能够在遇到算法题的时候自然而然的想到许多的数据结构思路来解决算法的相关问题。

  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

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

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值