堆和优先级队列3:不泡妹子都要会的LeetCode7道题之一
作者:互联网
我们前面费了那么大篇幅介绍堆和优先级队列,就是为了分析本文的7道题。而这7道题,
特别!特别!特别!
重要!重要!重要!
不过呢,正如我们前面说的,这些题目其实知道怎么处理就行,不必一定要写出来。
1.Leetcode23 合并K个排序链表
这个题我们前面曾经花了很大的篇幅来介绍,其中提到一句说可以使用优先级队列来做。原理是不管几个链表,最终都是按照顺序来的。如果使用优先级队列,每次都将剩余节点的最小值加到输出链表尾部,最后堆空的时候,合并也就完成了。
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0) return null;
PriorityQueue<ListNode> q = new PriorityQueue<>(Comparator.comparing(node -> node.val));
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null) {
q.add(lists[i]);
}
}
ListNode dummy = new ListNode(0);
ListNode tail = dummy;
while (!q.isEmpty()) {
tail.next = q.poll();
tail = tail.next;
if (tail.next != null) {
q.add(tail.next);
}
}
return dummy.next;
}
2.LeetCode239 滑动窗口最大值
类似题:剑指offer59
这个题其实就是每次框移动的时候,都输出框的最大值。框的大小就是堆的大小,滑动窗口移动的时候,堆就跟着调整元素,。
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || k <= 0) return new int[0];
int[] res = new int[nums.length - k + 1];
ArrayDeque<Integer> deque = new ArrayDeque<Integer>>();
int index = 0;
for (int i = 0; i < nums.length; i++) {
while (!deque.isEmpty()&& deque.peek()< i - k + 1) // Ensure deque's size doesn't exceed k
deque.poll();
// Remove numbers smaller than a[i] from right(a[i-1]) to left, to make the first number in the deque the largest one in the window
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i])
deque.pollLast();
deque.offer(i);// Offer the current index to the deque's tail
if (i >= k - 1)// Starts recording when i is big enough to make the window has k elements
res[index++] = nums[deque.peek()];
}
return res;
}
3.leetcode 264 丑数II
也是剑指offer49题
使用最小堆,要得到从小到大的第 nn 个丑数,可以使用最小堆实现。
初始时堆为空。首先将最小的丑数 11 加入堆。
每次取出堆顶元素 xx,则 xx 是堆中最小的丑数,由于 2x, 3x, 5x2x,3x,5x 也是丑数,因此将 2x, 3x, 5x2x,3x,5x 加入堆。
上述做法会导致堆中出现重复元素的情况。为了避免重复元素,可以使用哈希集合去重,避免相同元素多次加入堆。
在排除重复元素的情况下,第 nn 次从最小堆中取出的元素即为第 nn 个丑数。
class Solution {
public int nthUglyNumber(int n) {
int[] factors = {2, 3, 5};
Set<Long> seen = new HashSet<Long>();
PriorityQueue<Long> heap = new PriorityQueue<Long>();
seen.add(1L);
heap.offer(1L);
int ugly = 0;
for (int i = 0; i < n; i++) {
long curr = heap.poll();
ugly = (int) curr;
for (int factor : factors) {
long next = curr * factor;
if (seen.add(next)) {
heap.offer(next);
}
}
}
return ugly;
}
}
4.LeetCode347 前K个高频元素
使用堆的思路与算法
首先遍历整个数组,并使用哈希表记录每个数字出现的次数,并形成一个「出现次数数组」。找出原数组的前 k 个高频元素,就相当于找出「出现次数数组」的前 k 大的值。
最简单的做法是给「出现次数数组」排序。但由于可能有O(N) 个不同的出现次数(其中 NN 为原数组长度),故总的算法复杂度会达到 O(NlogN),不满足题目的要求。
在这里,我们可以利用堆的思想:建立一个小顶堆,然后遍历「出现次数数组」:
如果堆的元素个数小于 k,就可以直接插入堆中。
如果堆的元素个数等于 k,则检查堆顶与当前出现次数的大小。如果堆顶更大,说明至少有 k 个数字的出现次数比当前值大,故舍弃当前值;否则,就弹出堆顶,并将当前值插入堆中。
遍历完成后,堆中的元素就代表了「出现次数数组」中前 k 大的值。
class Solution {
public int[] topKFrequent(int[] nums, int k) {
Map<Integer, Integer> occurrences = new HashMap<Integer, Integer>();
for (int num : nums) {
occurrences.put(num, occurrences.getOrDefault(num, 0) + 1);
}
// int[] 的第一个元素代表数组的值,第二个元素代表了该值出现的次数
PriorityQueue<int[]> queue = new PriorityQueue<int[]>(new Comparator<int[]>() {
public int compare(int[] m, int[] n) {
return m[1] - n[1];
}
});
for (Map.Entry<Integer, Integer> entry : occurrences.entrySet()) {
int num = entry.getKey(), count = entry.getValue();
if (queue.size() == k) {
if (queue.peek()[1] < count) {
queue.poll();
queue.offer(new int[]{num, count});
}
} else {
queue.offer(new int[]{num, count});
}
}
int[] ret = new int[k];
for (int i = 0; i < k; ++i) {
ret[i] = queue.poll()[0];
}
return ret;
}
}
5.LeetCode692 前K个高频单词
这个有点复杂,如果用小根堆解决问题的方法是:
先用哈希表统计单词的出现频率,然后因为题目要求前 KK 大。所以构建一个 大小为 KK 的小根堆按照上述规则自定义排序的比较器。然后依次将单词加入堆中,当堆中的单词个数超过 KK 个后,我们需要弹出顶部最小的元素使得堆中始终保留 KK 个元素,遍历完成后剩余的 KK 个元素就是前 KK 大的。最后我们依次弹出堆中的 KK 个元素加入到所求的结果集合中。
代码不写了
标签:LeetCode7,道题,不泡,nums,deque,int,元素,next,new 来源: https://blog.csdn.net/xueyushenzhou/article/details/119223120