前言
堆是一个很有特点的排序方法,它可排序,也可只取前K个,配合K大的窗口。原生堆比优先队列快且灵活,原生堆的核心在于建堆和调整。
一、案例
二、K大窗口
1)优先队列来维持K大窗口
2)原生堆来维持K大窗口
3)List + 二分来确定第K个元素
package com.xhu.offer.offerII;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.PriorityQueue;
//数据流的第K大数值
public class KthLargest {
//list+二分
List<Integer> cache = new ArrayList<>();
int k;
public KthLargest(int k, int[] nums) {
this.k = k;
Arrays.sort(nums);
for (int num : nums) cache.add(num);
}
public int add(int val) {
int idx = binarySearch(this.cache, val);
cache.add(idx, val);
return cache.get(cache.size() - k);
}
//统一的二分
private int binarySearch(List<Integer> cache, int target) {
int low = 0, high = cache.size();
while (low < high) {
int mid = low + high >>> 1;
int midVal = cache.get(mid);
if (midVal < target) low = mid + 1;
else high = mid;
}
return high;
}
}
class KthLargest2 {
//维持一个K大窗口即可,原生堆,不难,而且速度自然比优先队列快,加上是自定义,灵活。
int[] nums;
public KthLargest2(int k, int[] nums) {
this.nums = new int[k];
this.nums[k - 1] = Integer.MIN_VALUE;
int i = 0;
for (; i < k && i < nums.length; i++) this.nums[i] = nums[i];
//建堆
for (int j = k >>> 1; j >= 0; j--) adjustHeap(j);
//将堆顶的小元素去掉,维持K大的堆。
while (i++ < nums.length) {
if (nums[i - 1] < this.nums[0]) continue;
this.nums[0] = nums[i - 1];
adjustHeap(0);
}
}
private void adjustHeap(int k) {
int val = nums[k];
for (int i = 2 * k + 1; i < nums.length; i = i * 2 + 1) {
if (i + 1 < nums.length && nums[i] > nums[i + 1]) i++;
if (nums[i] > val) break;
nums[k] = nums[i];
k = i;
}
nums[k] = val;
}
public int add(int val) {
if (val > nums[0]) {
nums[0] = val;
adjustHeap(0);
}
return nums[0];
}
}
class KthLargest3 {
//维持一个K大窗口即可,优先队列
PriorityQueue<Integer> queue = new PriorityQueue<>();
int k;
public KthLargest3(int k, int[] nums) {
this.k = k;
Arrays.sort(nums);
int len = nums.length;
for (int i = len - 1; i >= 0 && i + k >= len; i--) queue.offer(nums[i]);
}
public int add(int val) {
queue.offer(val);
if (queue.size() > k) queue.poll();
return queue.peek();
}
}
总结
1)堆是一个很有特点的排序方法,它可排序,也可只取前K个,配合K大的窗口。
2)原生堆比优先队列快且灵活,原生堆的核心在于建堆和调整。
参考文献
[1] LeetCode 原题
附录
1、出现频率最高的K个数字
package com.xhu.offer.offerII;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.PriorityQueue;
//出现频率最高的K个数字
public class TopKFrequent {
//map统计个数,堆找前K个
public int[] topKFrequent(int[] nums, int k) {
PriorityQueue<Map.Entry<Integer, Integer>> queue = new PriorityQueue<>((o1, o2) -> o2.getValue() - o1.getValue());
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) queue.offer(iterator.next());
int[] res = new int[k];
int count = 0;
while (count++ < k) res[count - 1] = queue.poll().getKey();
return res;
}
//map统计个数,原生堆找前K个
public int[] topKFrequent2(int[] nums, int k) {
int[][] cache = new int[2][k];
int size = 0;
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums) map.put(num, map.getOrDefault(num, 0) + 1);
Iterator<Map.Entry<Integer, Integer>> iterator = map.entrySet().iterator();
while (iterator.hasNext() && size < k) {
Map.Entry<Integer, Integer> next = iterator.next();
cache[0][size] = next.getKey();
cache[1][size++] = next.getValue();
}
//建堆
for (int i = k >>> 1; i >= 0; i--) adjustHeap(cache, i);
//维持K大窗口
while (iterator.hasNext()) {
Map.Entry<Integer, Integer> next = iterator.next();
if (next.getValue() < cache[1][0]) continue;
cache[0][0] = next.getKey();
cache[1][0] = next.getValue();
adjustHeap(cache, 0);
}
return cache[0];
}
private void adjustHeap(int[][] nums, int k) {
int val = nums[1][k];
int key = nums[0][k];
for (int i = 2 * k + 1; i < nums[0].length; i = i * 2 + 1) {
if (i + 1 < nums[0].length && nums[1][i] > nums[1][i + 1]) i++;
if (nums[1][i] > val) break;
nums[0][k] = nums[0][i];
nums[1][k] = nums[1][i];
k = i;
}
nums[0][k] = key;
nums[1][k] = val;
}
}
2、和最小的K个数对
对于堆问题,也可以用K大的list窗口来二分来解决,毕竟二分也是O(logN)
package com.xhu.offer.offerII;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
//和最小的k个数对
public class KSmallestPairs {
//
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
List<List<Integer>> res = new ArrayList<>();
int i = 0;
for (; res.size() < k && i < nums1.length; i++) {
for (int j = 0; res.size() < k && j < nums2.length; j++) {
List<Integer> el = new ArrayList<>();
el.add(nums1[i]);
el.add(nums2[j]);
res.add(el);
}
}
res.sort((o1, o2) -> o1.get(0) + o1.get(1) - o2.get(0) - o2.get(1));
for (; i < nums1.length; i++) {
for (int j = 0; j < nums2.length; j++) {
int m = res.get(res.size() - 1).get(0) + res.get(res.size() - 1).get(1);
int n = nums1[i] + nums2[j];
if (n >= m) break;
int idx = binarySearch(res, n);
res.remove(res.size() - 1);
List<Integer> el = new ArrayList<>();
el.add(nums1[i]);
el.add(nums2[j]);
res.add(idx, el);
}
}
return res;
}
private int binarySearch(List<List<Integer>> var, int target) {
int low = 0, high = var.size() - 1;
while (low < high) {
int mid = low + high >>> 1;
int midVal = var.get(mid).get(0) + var.get(mid).get(1);
if (midVal > target) high = mid - 1;
else low = mid + 1;
}
return low;
}
public static void main(String[] args) {
List<Integer> el = new ArrayList<>();
el.add(1);
el.add(0, 0);
System.out.println(Arrays.toString(el.toArray()));
}
}