题目描述
输入n个整数,找出其中最小的K个数。例如输入4,5,1,6,2,7,3,8这8个数字,则最小的4个数字是1,2,3,4。
方式一:小顶堆
public class TopKMin {
public static void main(String[] args) {
int[] arr = {7,8,2,5,1,3,0,4,6,9,-1};
int[] res = topKMin(arr, 4);
System.out.println(Arrays.toString(res));
}
private static int[] topKMin(int[] arr, int k) {
if (arr == null || k <= 0 || arr.length == 0) {
return new int[0];
}
Queue<Integer> queue = new PriorityQueue<>(k);
for (int i = 0; i < arr.length; i++) {
queue.add(arr[i]);
}
int[] res = new int[k];
int i = 0;
while (i < k) {
res[i++] = queue.poll();
}
return res;
}
}
方式二:大顶堆
public class TopKMin {
public static void main(String[] args) {
int[] arr = {7,8,2,5,1,3,0,4,6,9,-1};
int[] res = topKMin(arr, 4);
System.out.println(Arrays.toString(res));
}
private static int[] topKMin(int[] arr, int k) {
if (arr == null || k <= 0 || arr.length == 0) {
return new int[0];
}
Queue<Integer> queue = new PriorityQueue<>(k, (Integer i1, Integer i2) -> i2 - i1);
int i = 0;
while (i < k) {
queue.add(arr[i]);
i ++;
}
for (i = k; i < arr.length; i++) {
Integer curMax = queue.peek();
if (curMax > arr[i]) {
queue.poll();
queue.add(arr[i]);
}
}
int[] res = new int[k];
i = 0;
while (queue.size() > 0) {
res[i++] = queue.poll();
}
return res;
}
}
public class KThFromTheBottom {
public static void main(String[] args) {
int[] arr = {7,8,2,5,1,3,0,4,6,9,-1};
System.out.println(topKMin(arr, 4));
System.out.println(topKMax(arr, 4));
}
private static Queue<Integer> topKMax(int[] arr, int k) {
// 1.小顶堆
Queue<Integer> queue = new PriorityQueue<>(k, Comparator.comparingInt(i -> i));
for (int i = 0; i < arr.length; i++) {
int val = arr[i];
if (queue.size() < k) {
queue.add(val);
} else {
// 2.堆已经有k个值了,先peek下堆中最小的值是否 < 待加入值
// 是则弹出,加入当前值(否则说明待加入值太小了,比堆中目前最小值还小,没必要加入queue)
Integer peek = queue.peek();
if (peek!= null && val >= peek) {
queue.poll();
queue.add(val);
}
}
}
return queue;
}
private static Queue<Integer> topKMin(int[] array, int k) {
// 1.大顶堆
Queue<Integer> queue = new PriorityQueue<>(k, (i1, i2) -> i2 - i1);
for (int i = 0; i < array.length; i++) {
int val = array[i];
if (queue.size() < k) {
queue.add(val);
} else {
// 2.堆已经有k个值了,先peek下堆中最大的值是否 > 待加入值
// 是则弹出,加入当前值即换血更年轻的(否则说明待加入值太大了,比堆中目前最大值还大,没必要加入queue)
Integer peek = queue.peek();
if (peek!= null && peek >= val) {
queue.poll();
queue.add(val);
}
}
}
return queue;
}
}
补充topk问题
- 一篇文章中出现频率最高的10个单词
1.通过IO流,读取此文章,用StringBuilder存读入的单词hello,world(sb.append(元素+“,”))
2.将StringBuilder->String->StringTokenizer
StringBuilder sb = new StringBuilder();
StringTokenizer tokenizer = new StringTokenizer(sb.toString());
Map<String,Integer> map = new HashMap<>();
while (tokenizer.hasMoreTokens()){
String str = tokenizer.nextToken();
Integer count = map.get(str);
if (count == null){
map.put(str,1);
}else {
map.put(str,count++);
}
}
3.遍历map,将map中的所有val存入大顶堆
4.直接从大顶堆中拿出top10个元素,即为出现频率前10的单词
- 如果是1GB的内容,找topk问题。
1.采用归并算法的思想:先将1GB分成n份,堆每一份求出topk
2.再对这n份的topk结果,再进行一次总体topk即可
3.比如1GB分成100份,每份求出对应的top10,最终将100*top10再进行一次求top10即可
- 总结:
1.求频率、出现次数:可以用map存储元素和该元素出现的次数
2.求topk问题,可以用顶堆
3.数据量巨大的情况下,可以分治,先分,后合