优先队列 -leetcode-347.前 K 个高频元素

347. 前 K 个高频元素

题目描述

给你一个整数数组 nums 和一个整数 k ,请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。

示例 1:

输入: nums = [1,1,1,2,2,3], k = 2
输出: [1,2]

解法选择:

  • 优先队列
    • top k 问题,优先队列是一把好手

优先队列

  • 底层基于堆实现。堆是一种完全二叉树,其父节点大于(或小于)所有子节点,即大根堆(或小根堆)
  • 每次增/删后,保持栈顶元素是 max(或 min)的
    • 仅保持局部是有序的,并不是全部有序

注意:

  • 到底选用大顶堆还是小顶堆?
    • 一开始我就想也没想,题目要求 top k,那“铁定”大顶堆啊。结果,就是直接翻车 WA,调试发现不对
    • 后来一想如果选大顶堆的话,那么,每次出队列弹出的都是max的元素,必然无法使得保留在优先队列中的最终k个是top k(人家都被弹了,还保留个 der 的 top k)
    • 所以,铁定的选择小顶堆。每次将当前元素 value 和当前队头元素对应的 value 比较,如果大于之,则出队;否则,啥也不管

思路:

  1. 使用 map 记录nums中元素出现的个数;
    • key:nums[i];value:nums[i] 出现的次数
  2. 将 map 中前 k 个元素的 key 存入 PriorityQueue;
  3. 将 map 中剩余的 size - k 个元素,与队头元素 peek 比较:
    • 如果 map 中当前元素的 value 大于 peek 在 map 中的 value,则将当前元素 key 入队;
    • 否则,不做任何操作。
  4. 倒序(因为每次取得都是最小的)从小顶堆中取出k个元素,作为最终结果
    • 但题目,其实没有要求输出顺序(扩展一下而已)
class Solution {
    public int[] topKFrequent(int[] nums, int k) { 
		// 记录nums中元素出现的个数
		Map<Integer, Integer> map = new HashMap<Integer, Integer>();
		for (int i : nums) {
			if (!map.containsKey(i)) {
				map.put(i, 1);
			} else {
				map.put(i, map.get(i) + 1);
			}
		}
		
		// 优先队列(小顶堆k个元素)
		Queue<Integer> queue = new PriorityQueue<>(new Comparator<Integer>() {
			@Override
			public int compare(Integer a, Integer b) {
				return map.get(a) - map.get(b); // // 排序规则(出现次数升序)
			}
		}); 
		
		// 遍历 map,用小顶堆保存频率最大的k个元素
		for (Integer key : map.keySet()) {
			if (queue.size() < k) { // 堆里小于k个元素时候,直接填入
				queue.add(key);
			} else if (map.get(key) > map.get(queue.peek())) { // 核心:保证小根堆里面只有k个元素,并且当前队列元素是最大的
				queue.remove();
				queue.add(key);
			}
		}
		
		// 倒序(因为每次取得都是最小的)从小顶堆中取出k个元素,作为最终结果
        // 但题目,其实没有要求输出顺序(扩展一下而已)
		int cnt = 0; // ans的当前位置
		int[] ans = new int[k]; // 最终答案
		for (int i = k - 1; i >= 0; i--) {
            ans[i] = queue.poll();
        }
		return ans;
    }
}

703. 数据流中的第 K 大元素

题目描述

设计一个找到数据流中第 k 大元素的类(class)。注意是排序后的第 k 大元素,不是第 k 个不同的元素。
请实现 KthLargest 类:

  • KthLargest(int k, int[] nums) 使用整数 k 和整数流 nums 初始化对象。
  • int add(int val) 将 val 插入数据流 nums 后,返回当前数据流中第 k 大的元素。
    优先队列 -leetcode-347.前 K 个高频元素_第1张图片
    刚才 347. 前 K 个高频元素 求 top k 问题,优先队列(即,堆)是一把好手。而本题 仍然需要用到

那么到底用 “大顶堆” 还是 “小顶堆” 呢 ?

  • 答:小顶堆。构造一个小顶堆,堆中维护 k 个元素,则当前堆顶为这 k 个元素的最小值,也即第 k 大元素.

add(int val)

  • 由于可能出现 nums 中元素个数小于 k 的情况,如果当前 堆中还不够 k 个元素,则直接把 val 插入,返回新的堆顶元素,即可;
  • 否则,如果 当前元素比堆顶元素(即,此时的第 k 大元素)大,则将当前堆顶元素弹出,并将 val 元素插入,val 插入后 新的堆顶元素就是第 k 大元素;
  • 否则,即当前元素比堆顶元素(即第k大元素)小 时,则此时堆顶元素即为第 k 大元素,不用做任何操作
  • 最终,堆顶元素就是第 k 大元素
class KthLargest {
    int k;
    int[] nums;
    PriorityQueue<Integer> queue;

    public KthLargest(int k, int[] nums) {
        this.k = k;
        this.nums = nums;
        // 小根堆。堆中维护 k 个元素,则当前堆顶为这 k 个元素的最小值,也即第 k 大元素
        queue = new PriorityQueue<Integer>((o1, o2) -> o1 - o2);
        /**
        // 前 k 个元素
        for (int i = 0; i < k && i < nums.length; i++) {
            queue.offer(nums[i]);
        }
        // 后 nums.length - k 个
        for (int i = k; i < nums.length; i++) {
            if (nums[i] > queue.peek()) { // 当前元素比 最小堆的堆顶元素大,则插入,并将堆顶元素删除
                queue.poll();
                queue.offer(nums[i]);
            }
        }
        */
        // 小顶堆 初始化(这样写 比上面两个 for 初始化更简便)
        for (int i = 0; i < nums.length; i++) {
            add(nums[i]);
        }
    }

    public int add(int val) {
        // 如果当前 堆中还不够 k 个元素,则直接把 val 插入,返回新的堆顶元素,即可
        if (queue.size() < k) {
            queue.offer(val);
        } else if (val > queue.peek()) {  // 当前元素比堆顶元素(即,此时的第 k 大元素)大,则将当前堆顶元素弹出,并将 val 元素插入,val 插入后 新的堆顶元素就是第 k 大元素
            queue.poll(); // 之前的堆顶元素弹出
            queue.offer(val); // 插入 val
        }
        // 当前元素比堆顶元素(即第k大元素)小,则此时堆顶元素即为第 k 大元素,不用做任何操作
        return queue.peek(); // 当前堆顶元素就是第 k 大元素
    }
}

313. 超级丑数

题目描述

超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。

给你一个整数 n 和一个整数数组 primes ,返回第 n 个 超级丑数 。

题目数据保证第 n 个 超级丑数 在 32-bit 带符号整数范围内。

解法选择:

  • 优先队列 + set 去重

思路:

  • 由于超级丑数 的所有质因数都出现在质数数组 primes 中,所以数数组 primes 中两两元素乘积 必然是 超级丑数;
  • 所以每次取当前已有的 min 丑数,并与 primes 所有元素相乘,即为下一批 超级丑数(可能重复,所以要用 set 去重
  • 每次取每次取当前已有的 min 丑数进行下一步操作,而 最小堆 (优先队列)在这方面效率较高
class Solution {
    public int nthSuperUglyNumber(int n, int[] primes) {
        int res = -1;
        PriorityQueue<Long> minHead = new PriorityQueue<>(); // 小根堆
        Set<Long> set = new HashSet<>();
        minHead.offer(1L);
        while ((n--) > 0) {
            // System.out.println("minHead = " + minHead);
            long top = minHead.poll();
            res = (int)top; // 记录当前小根堆队头元素,即min
            for (int prime : primes) {
                // 去重
                if (set.add(top * prime)) {
                    minHead.offer(top * prime);
                }
            }
        }
        return res;
    }
}

你可能感兴趣的:(leetcode,leetcode,队列,数据结构)