1、优先队列的经典问题,在1000000个元素中选出前100名元素,题型模式如在N个元素中选出前M个元素。
在这里面的关键就是M远远小于N的,如果M是1,是很简单的,只需要遍历一遍,此时时间复杂度是O(n)级别的,但是此时要选出前M个元素,如果M不等于1的话,就有点麻烦了,此时也可以将100万个元素进行一下排序,对于100万个元素,使用归并排序还是快速排序,都可以在O(NlogN)的时间复杂度内完成任务,排序之后可以直接取出前一百名元素。
但是如果使用优先队列的话,可以在O(NlogM)的时间复杂度内解决问题。此时O(NlogM)比O(NlogN)时间复杂度更好的。
如何使用优先队列解决这个问题呢,可以使用优先队列,维护当前看到的前M个元素。对于一百万个元素,也就是对于这N个元素,肯定是要从头到尾扫描一遍的,在扫描的过程中,我们首相将这N个元素中的前M个元素放进优先队列里面,之后每次看到一个新的元素,如果这个新的元素比当前的这个优先队列中最小的那个元素还要大的话,那么,我们就将这个优先队列中最小的那个元素扔出去,取而代之的换上我们这个新的元素,用这样的方式,相当于这个优先队列中一直维护着当前可以看到的前M个元素,直到我们将这N个元素全部扫描完,在优先队列中最终留下了这M个元素就是我们要求得这个结果。
需要注意的是这里虽然要选出前M个元素,前M个元素默认是前M个最大的元素,但是实际上需要的是一个最小堆,我们要能够非常快速的取出当前看到的前M个元素中的最小的那个元素,我们不断的将当前可以看到的前M大的元素中那个最小的元素进行替换,此时,这里需要一个最小堆,但是呢,实际上,解决这个问题,并不需要真的使用一个最小堆的,这里依然使用最大堆。
这里面的关键就是,什么是优先级,并不是规定越大的元素优先级越高的,事实上,在这个例子中,由于每次都要先取出优先队列中最小的那个元素,所以,实质上,这里完全可以自己去定义,元素的值越小它的优先级越高,在这样的一个定义下,依然可以使用优先队列的底层是一个最大堆,却依旧完成了这个问题。大和小其实是相对的。
2、给定一个非空的整数数组,返回其中出现频率前 k 高的元素。
示例 1:
1 输入: nums = [1,1,1,2,2,3], k = 2 2 输出: [1,2]
示例 2:
1 输入: nums = [1], k = 1 2 输出: [1]
说明:
- 你可以假设给定的 k 总是合理的,且 1 ≤ k ≤ 数组中不相同的元素的个数。
- 你的算法的时间复杂度必须优于 O(n log n) , n 是数组的大小。
3、自己设计的优先队列,如下所示:
1 package com.queue; 2 3 import com.maxHeap.MaxHeap; 4 5 /** 6 * 优先队列的底层实现可以使用最大堆进行实现, 7 * 由于优先队列本身就是一个队列,所以可以复用队列的接口。 8 * 9 * @param <E> 由于优先队列要具有可比较性,所以要进行继承Comparable 10 */ 11 public class PriorityQueue<E extends Comparable<E>> implements Queue<E> { 12 13 // 使用最大堆实现优先队列 14 private MaxHeap<E> maxHeap; 15 16 /** 17 * 无参构造函数,直接创建一个MaxHeap即可 18 */ 19 public PriorityQueue() { 20 maxHeap = new MaxHeap<>(); 21 } 22 23 @Override 24 public void enqueue(E e) { 25 // 入队操作,直接调用最大堆的add方法 26 maxHeap.add(e); 27 } 28 29 @Override 30 public E dequeue() { 31 // 出队操作,将最大元素提取出来 32 return maxHeap.extractMax(); 33 } 34 35 @Override 36 public E getFront() { 37 // 查看队首的元素是谁 38 // 其实对应的是最大堆对应的堆顶的元素 39 E maxHeapMax = maxHeap.findMax(); 40 // 获取到队列中队首的元素。对应栈的查看栈顶的元素。 41 return maxHeapMax; 42 } 43 44 @Override 45 public int getSize() { 46 // 直接返回最大堆中的大小 47 return maxHeap.size(); 48 } 49 50 @Override 51 public boolean isEmpty() { 52 // 直接返回最大堆中的是否为空 53 return maxHeap.isEmpty(); 54 } 55 56 57 public static void main(String[] args) { 58 PriorityQueue<Integer> priorityQueue = new PriorityQueue<>(); 59 // 优先队列入队操作 60 for (int i = 0; i < 1000; i++) { 61 priorityQueue.enqueue(i); 62 } 63 64 65 // 优先队列出队操作 66 for (int i = 0; i < 1000; i++) { 67 if (i % 30 == 0) { 68 System.out.println(); 69 } 70 System.out.print(priorityQueue.dequeue() + " "); 71 } 72 73 74 } 75 76 }
4、解决力扣问题的代码,如下所示:
1 package com.leetcode; 2 3 import com.queue.PriorityQueue; 4 5 import java.util.*; 6 7 /** 8 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 9 */ 10 public class HighFrequency { 11 12 /** 13 * 私有内部类,频次类 14 * <p> 15 * <p> 16 * 优先队列的泛型元素是要具有可比较性的,所以实现可比较接口 17 */ 18 private class Frequency implements Comparable<Frequency> { 19 public int e;// 元素内容 20 public int frequency;// 元素的频次 21 22 /** 23 * 含参构造函数 24 * 25 * @param e 26 * @param frequency 27 */ 28 public Frequency(int e, int frequency) { 29 this.e = e; 30 this.frequency = frequency; 31 } 32 33 /** 34 * 重写比较的方法 35 * <p> 36 * <p> 37 * 将当前类对象和另外一个频次的类对象进行比较 38 * 39 * @param another 40 * @return 41 */ 42 @Override 43 public int compareTo(Frequency another) { 44 // 对于优先队列,要非常容易的取出频次较低的那个元素 45 // 这里可以定义优先级,可以定义什么是优先级高 46 // 这里定义,频次越低优先级越高 47 if (this.frequency < another.frequency) { 48 // Java的compareTo方法,当前元素比传进来的元素大返回1,小的话,返回-1,等于的话返回0; 49 50 // 而此处的设计是,当前元素的频次小于传进来的元素,就是当前元素频次低的话,返回1, 51 // 这里定义的优先级高的意思,是频次低的优先级高, 52 // 对于优先队列,底层虽然是最大堆,取出优先级高的那个元素,但是这个优先级最高的这个元素是频次最低的那个元素。 53 return 1; 54 } else if (this.frequency > another.frequency) { 55 // 如果当前元素大于传进去的元素,此时是返回-1,就是优先级低的 56 return -1; 57 } else { 58 // 如果当前元素相等传进去的元素此时是返回0 59 return 0; 60 } 61 } 62 } 63 64 /** 65 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 66 * 67 * @param nums 非空数组 68 * @param k 前k高的元素 69 * @return 将前k高的集合返回 70 */ 71 public List<Integer> topKFrequent(int[] nums, int k) { 72 // 创建一个map对象用于统计数组元素的频数 73 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 74 // 循环遍历数组 75 for (int num : nums) { 76 // 如果数组元素已经存储在map集合中 77 if (map.containsKey(num)) { 78 // 此时将num的value值加一。 79 map.put(num, map.get(num) + 1); 80 } else { 81 map.put(num, 1); 82 } 83 } 84 85 // 此时Map集合中保存了数组中每一个元素以及每一个元素的频数 86 // 此时,求出,前k个频数最高的元素 87 // 由于此时,优先队列承载的元素类型是什么类型呢,此时map是键值对的形式 88 // 要依据元素所对应的频类来决定优先级,与此同时,也关心频率对应的元素是谁 89 // 最后,要将这些元素返回。 90 PriorityQueue<Frequency> priorityQueue = new PriorityQueue<Frequency>(); 91 // 遍历map集合的key 92 for (int key : map.keySet()) { 93 // 如果优先队列的元素数是小于k的,此时还没有存够k个元素,直接进行入队 94 if (priorityQueue.getSize() < k) { 95 // 直接对于key进行入队操作 96 priorityQueue.enqueue(new Frequency(key, map.get(k))); 97 98 99 // 队首的元素就是对于优先队列来说,优先级最高的那个元素, 100 // 在这里的优先级定义下,优先级最高的就是频次最低的那个元素, 101 } else if (map.get(key) > priorityQueue.getFront().frequency) { 102 // 此时,优先队列里面已经有k个元素了,是我们当前看到的前k个频次最高的元素, 103 // 此时,新遍历的一个key,此时这个key的频次可能比当前这前k个频次最高的元素中 104 // 那个频次最小的那个元素的频次更高。这种情况下,此时应该将优先队列中频次最小的那个元素替换掉。 105 106 // 此时,让队首元素出队 107 priorityQueue.dequeue(); 108 // 然后让新的元素入队, 109 priorityQueue.enqueue(new Frequency(k, map.get(key))); 110 } 111 112 } 113 114 // 循环遍历结束,优先队列里面剩下的就是频次最高的前K个元素。 115 // 这样的算法,时间复杂度的O(nlogk) 116 List<Integer> list = new LinkedList<Integer>(); 117 // for (int i = 0; i < priorityQueue.getSize(); i++) { 118 // list.add(priorityQueue.dequeue().e); 119 // } 120 121 while (!priorityQueue.isEmpty()) { 122 list.add(priorityQueue.dequeue().e); 123 } 124 // 将符合条件的元素返回即可。 125 return list; 126 } 127 128 public static void main(String[] args) { 129 int[] nums = new int[]{1, 1, 1, 2, 2, 3}; 130 int k = 3; 131 HighFrequency highFrequency = new HighFrequency(); 132 List<Integer> list = highFrequency.topKFrequent(nums, k); 133 for (int i = 0; i < list.size(); i++) { 134 System.out.println(list.get(i)); 135 } 136 } 137 138 }
5、如果使用java自带的优先队列,实现,如下所示:
1 package com.leetcode; 2 3 import java.util.*; 4 5 /** 6 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 7 */ 8 public class HighFrequency2 { 9 10 /** 11 * 私有内部类,频次类 12 * <p> 13 * <p> 14 * 优先队列的泛型元素是要具有可比较性的,所以实现可比较接口 15 */ 16 private class Frequency implements Comparable<Frequency> { 17 public int e;// 元素内容 18 public int frequency;// 元素的频次 19 20 /** 21 * 含参构造函数 22 * 23 * @param e 24 * @param frequency 25 */ 26 public Frequency(int e, int frequency) { 27 this.e = e; 28 this.frequency = frequency; 29 } 30 31 /** 32 * 重写比较的方法 33 * <p> 34 * <p> 35 * 将当前类对象和另外一个频次的类对象进行比较 36 * 37 * @param another 38 * @return 39 */ 40 @Override 41 public int compareTo(Frequency another) { 42 // 需要注意的是,java本身的PriorityQueue优先队列默认是最小堆。 43 // Java的compareTo方法,当前元素比传进来的元素大返回1,小的话,返回-1,等于的话返回0; 44 if (this.frequency < another.frequency) { 45 // 而此处的设计是,当前元素的频次小于传进来的元素,就是当前元素频次低的话,返回-1, 46 return -1; 47 } else if (this.frequency > another.frequency) { 48 // 49 return 1; 50 } else { 51 // 52 return 0; 53 } 54 } 55 } 56 57 // 如何改变Java标准库中的类相应的比较的方式 58 private class FrequencyComparator implements Comparator<Frequency> { 59 60 /** 61 * 最小堆,是最小的元素在堆顶,最大堆,最大的元素在堆顶。 62 * 63 * @param o1 64 * @param o2 65 * @return 66 */ 67 @Override 68 public int compare(Frequency o1, Frequency o2) { 69 // 谁的频率小,让谁靠前 70 // 定义a在b的前面即a小于b返回-1,a在b的后面即a大于b返回1,a和b相等返回0。 71 72 // 非常简便的实现方式,任意正数,负数都可以进行代表返回结果的 73 return o1.frequency - o2.frequency; 74 } 75 76 } 77 78 79 /** 80 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 81 * 82 * @param nums 非空数组 83 * @param k 前k高的元素 84 * @return 将前k高的集合返回 85 */ 86 public List<Integer> topKFrequent(int[] nums, int k) { 87 // 创建一个map对象用于统计数组元素的频数 88 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 89 // 循环遍历数组 90 for (int num : nums) { 91 // 如果数组元素已经存储在map集合中 92 if (map.containsKey(num)) { 93 // 此时将num的value值加一。 94 map.put(num, map.get(num) + 1); 95 } else { 96 map.put(num, 1); 97 } 98 } 99 100 // 需要注意的是,java本身的PriorityQueue是最小堆。 101 PriorityQueue<Frequency> priorityQueue = new PriorityQueue<Frequency>(); 102 // 遍历map集合的key 103 for (int key : map.keySet()) { 104 // 如果优先队列的元素数是小于k的,此时还没有存够k个元素,直接进行入队 105 if (priorityQueue.size() < k) { 106 // 直接对于key进行入队操作 107 priorityQueue.add(new Frequency(key, map.get(k))); 108 109 110 // 队首的元素就是对于优先队列来说,优先级最高的那个元素, 111 // 在这里的优先级定义下,优先级最高的就是频次最低的那个元素, 112 } else if (map.get(key) > priorityQueue.peek().frequency) { 113 // 此时,优先队列里面已经有k个元素了,是我们当前看到的前k个频次最高的元素, 114 // 此时,新遍历的一个key,此时这个key的频次可能比当前这前k个频次最高的元素中 115 // 那个频次最小的那个元素的频次更高。这种情况下,此时应该将优先队列中频次最小的那个元素替换掉。 116 117 // 此时,让队首元素出队 118 priorityQueue.remove(); 119 // 然后让新的元素入队, 120 priorityQueue.add(new Frequency(k, map.get(key))); 121 } 122 123 } 124 125 // 循环遍历结束,优先队列里面剩下的就是频次最高的前K个元素。 126 // 这样的算法,时间复杂度的O(nlogk) 127 List<Integer> list = new LinkedList<Integer>(); 128 while (!priorityQueue.isEmpty()) { 129 list.add(priorityQueue.remove().e); 130 } 131 // 将符合条件的元素返回即可。 132 return list; 133 } 134 135 public static void main(String[] args) { 136 int[] nums = new int[]{1, 1, 1, 2, 2, 3}; 137 int k = 3; 138 HighFrequency2 highFrequency = new HighFrequency2(); 139 List<Integer> list = highFrequency.topKFrequent(nums, k); 140 for (int i = 0; i < list.size(); i++) { 141 System.out.println(list.get(i)); 142 } 143 } 144 145 }
6、如何改变Java标准库中的类相应的比较的方式,可以自定义比较器的方式进行实现。
1 package com.leetcode; 2 3 4 import java.util.*; 5 6 /** 7 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 8 */ 9 public class HighFrequency2 { 10 11 /** 12 * 私有内部类,频次类 13 * <p> 14 * <p> 15 * 优先队列的泛型元素是要具有可比较性的,所以实现可比较接口 16 */ 17 private class Frequency { 18 public int e;// 元素内容 19 public int frequency;// 元素的频次 20 21 /** 22 * 含参构造函数 23 * 24 * @param e 25 * @param frequency 26 */ 27 public Frequency(int e, int frequency) { 28 this.e = e; 29 this.frequency = frequency; 30 } 31 32 } 33 34 // 如何改变Java标准库中的类相应的比较的方式,比较器 35 // 如果优先队列里面存储的就是字符串,java默认字符串大小比较是按照字典序进行比较的, 36 // 如果有一个自定义的字符串比较的方案,由于不能修改Java中字符串比较的方法的, 37 // 此时可以使用属于自己的比较器,定义自己的字符串比较,传给优先队列就行了. 38 private class FrequencyComparator implements Comparator<Frequency> { 39 40 /** 41 * 最小堆,是最小的元素在堆顶,最大堆,最大的元素在堆顶。 42 * 43 * @param o1 44 * @param o2 45 * @return 46 */ 47 @Override 48 public int compare(Frequency o1, Frequency o2) { 49 // 谁的频率小,让谁靠前 50 // 定义a在b的前面即a小于b返回-1,a在b的后面即a大于b返回1,a和b相等返回0。 51 52 // 非常简便的实现方式,任意正数,负数都可以进行代表返回结果的 53 return o1.frequency - o2.frequency; 54 } 55 56 } 57 58 59 /** 60 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 61 * 62 * @param nums 非空数组 63 * @param k 前k高的元素 64 * @return 将前k高的集合返回 65 */ 66 public List<Integer> topKFrequent(int[] nums, int k) { 67 // 创建一个map对象用于统计数组元素的频数 68 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 69 // 循环遍历数组 70 for (int num : nums) { 71 // 如果数组元素已经存储在map集合中 72 if (map.containsKey(num)) { 73 // 此时将num的value值加一。 74 map.put(num, map.get(num) + 1); 75 } else { 76 map.put(num, 1); 77 } 78 } 79 80 // 需要注意的是,java本身的PriorityQueue是最小堆。 81 // 可以直接将比较器,以参数的形式传递进去。 82 PriorityQueue<Frequency> priorityQueue = new PriorityQueue<Frequency>(new FrequencyComparator()); 83 // 遍历map集合的key 84 for (int key : map.keySet()) { 85 // 如果优先队列的元素数是小于k的,此时还没有存够k个元素,直接进行入队 86 if (priorityQueue.size() < k) { 87 // 直接对于key进行入队操作 88 priorityQueue.add(new Frequency(key, map.get(k))); 89 90 91 // 队首的元素就是对于优先队列来说,优先级最高的那个元素, 92 // 在这里的优先级定义下,优先级最高的就是频次最低的那个元素, 93 } else if (map.get(key) > priorityQueue.peek().frequency) { 94 // 此时,优先队列里面已经有k个元素了,是我们当前看到的前k个频次最高的元素, 95 // 此时,新遍历的一个key,此时这个key的频次可能比当前这前k个频次最高的元素中 96 // 那个频次最小的那个元素的频次更高。这种情况下,此时应该将优先队列中频次最小的那个元素替换掉。 97 98 // 此时,让队首元素出队 99 priorityQueue.remove(); 100 // 然后让新的元素入队, 101 priorityQueue.add(new Frequency(k, map.get(key))); 102 } 103 104 } 105 106 // 循环遍历结束,优先队列里面剩下的就是频次最高的前K个元素。 107 // 这样的算法,时间复杂度的O(nlogk) 108 List<Integer> list = new LinkedList<Integer>(); 109 while (!priorityQueue.isEmpty()) { 110 list.add(priorityQueue.remove().e); 111 } 112 // 将符合条件的元素返回即可。 113 return list; 114 } 115 116 public static void main(String[] args) { 117 int[] nums = new int[]{1, 1, 1, 2, 2, 3}; 118 int k = 3; 119 HighFrequency2 highFrequency = new HighFrequency2(); 120 List<Integer> list = highFrequency.topKFrequent(nums, k); 121 for (int i = 0; i < list.size(); i++) { 122 System.out.println(list.get(i)); 123 } 124 } 125 126 }
7、可以将内部类,比较器,声明为匿名内部类来实现这个功能。
1 package com.leetcode; 2 3 4 import java.util.*; 5 6 /** 7 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 8 */ 9 public class HighFrequency2 { 10 11 /** 12 * 私有内部类,频次类 13 * <p> 14 * <p> 15 * 优先队列的泛型元素是要具有可比较性的,所以实现可比较接口 16 */ 17 private class Frequency { 18 public int e;// 元素内容 19 public int frequency;// 元素的频次 20 21 /** 22 * 含参构造函数 23 * 24 * @param e 25 * @param frequency 26 */ 27 public Frequency(int e, int frequency) { 28 this.e = e; 29 this.frequency = frequency; 30 } 31 32 } 33 34 // 如何改变Java标准库中的类相应的比较的方式,比较器 35 // 如果优先队列里面存储的就是字符串,java默认字符串大小比较是按照字典序进行比较的, 36 // 如果有一个自定义的字符串比较的方案,由于不能修改Java中字符串比较的方法的, 37 // 此时可以使用属于自己的比较器,定义自己的字符串比较,传给优先队列就行了. 38 // private class FrequencyComparator implements Comparator<Frequency> { 39 // 40 // /** 41 // * 最小堆,是最小的元素在堆顶,最大堆,最大的元素在堆顶。 42 // * 43 // * @param o1 44 // * @param o2 45 // * @return 46 // */ 47 // @Override 48 // public int compare(Frequency o1, Frequency o2) { 49 // // 谁的频率小,让谁靠前 50 // // 定义a在b的前面即a小于b返回-1,a在b的后面即a大于b返回1,a和b相等返回0。 51 // 52 // // 非常简便的实现方式,任意正数,负数都可以进行代表返回结果的 53 // return o1.frequency - o2.frequency; 54 // } 55 // 56 // } 57 58 59 /** 60 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 61 * 62 * @param nums 非空数组 63 * @param k 前k高的元素 64 * @return 将前k高的集合返回 65 */ 66 public List<Integer> topKFrequent(int[] nums, int k) { 67 // 创建一个map对象用于统计数组元素的频数 68 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 69 // 循环遍历数组 70 for (int num : nums) { 71 // 如果数组元素已经存储在map集合中 72 if (map.containsKey(num)) { 73 // 此时将num的value值加一。 74 map.put(num, map.get(num) + 1); 75 } else { 76 map.put(num, 1); 77 } 78 } 79 80 // 需要注意的是,java本身的PriorityQueue是最小堆。 81 // 可以直接将比较器,以参数的形式传递进去。 82 // 这里也可以直接使用匿名内部类,来实现比较器的方法 83 PriorityQueue<Frequency> priorityQueue = new PriorityQueue<Frequency>(new Comparator<Frequency>() { 84 85 @Override 86 public int compare(Frequency o1, Frequency o2) { 87 return o1.frequency - o2.frequency; 88 } 89 }); 90 // 遍历map集合的key 91 for (int key : map.keySet()) { 92 // 如果优先队列的元素数是小于k的,此时还没有存够k个元素,直接进行入队 93 if (priorityQueue.size() < k) { 94 // 直接对于key进行入队操作 95 priorityQueue.add(new Frequency(key, map.get(k))); 96 97 98 // 队首的元素就是对于优先队列来说,优先级最高的那个元素, 99 // 在这里的优先级定义下,优先级最高的就是频次最低的那个元素, 100 } else if (map.get(key) > priorityQueue.peek().frequency) { 101 // 此时,优先队列里面已经有k个元素了,是我们当前看到的前k个频次最高的元素, 102 // 此时,新遍历的一个key,此时这个key的频次可能比当前这前k个频次最高的元素中 103 // 那个频次最小的那个元素的频次更高。这种情况下,此时应该将优先队列中频次最小的那个元素替换掉。 104 105 // 此时,让队首元素出队 106 priorityQueue.remove(); 107 // 然后让新的元素入队, 108 priorityQueue.add(new Frequency(k, map.get(key))); 109 } 110 111 } 112 113 // 循环遍历结束,优先队列里面剩下的就是频次最高的前K个元素。 114 // 这样的算法,时间复杂度的O(nlogk) 115 List<Integer> list = new LinkedList<Integer>(); 116 while (!priorityQueue.isEmpty()) { 117 list.add(priorityQueue.remove().e); 118 } 119 // 将符合条件的元素返回即可。 120 return list; 121 } 122 123 public static void main(String[] args) { 124 int[] nums = new int[]{1, 1, 1, 2, 2, 3}; 125 int k = 3; 126 HighFrequency2 highFrequency = new HighFrequency2(); 127 List<Integer> list = highFrequency.topKFrequent(nums, k); 128 for (int i = 0; i < list.size(); i++) { 129 System.out.println(list.get(i)); 130 } 131 } 132 133 }
8、可以使用匿名内部类,来改变java内置的类型,改变了两个整形之间的比较逻辑。
1 package com.leetcode; 2 3 4 import java.util.*; 5 6 /** 7 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 8 */ 9 public class HighFrequency2 { 10 11 /** 12 * 给定一个非空的整数数组,返回其中出现频率前 k 高的元素。 13 * 14 * @param nums 非空数组 15 * @param k 前k高的元素 16 * @return 将前k高的集合返回 17 */ 18 public List<Integer> topKFrequent(int[] nums, int k) { 19 // 创建一个map对象用于统计数组元素的频数 20 Map<Integer, Integer> map = new TreeMap<Integer, Integer>(); 21 // 循环遍历数组 22 for (int num : nums) { 23 // 如果数组元素已经存储在map集合中 24 if (map.containsKey(num)) { 25 // 此时将num的value值加一。 26 map.put(num, map.get(num) + 1); 27 } else { 28 map.put(num, 1); 29 } 30 } 31 32 // 需要注意的是,java本身的PriorityQueue是最小堆。 33 // 可以直接将比较器,以参数的形式传递进去。 34 // 这里也可以直接使用匿名内部类,来实现比较器的方法 35 // 此时,匿名内部类就变成了topKFrequent方法内部的局部变量,此时具有变量捕获的能力,可以捕获不可改变的变量 36 PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(new Comparator<Integer>() { 37 38 /** 39 * 比较的方式是按照频类进行比较的 40 * 41 * 此时比较的是o1,o2,在map中的映射值的大小,就是他们的频类大小. 42 * @param o2 43 * @return 44 */ 45 @Override 46 public int compare(Integer o1, Integer o2) { 47 return map.get(o1) - map.get(o2); 48 } 49 }); 50 // 遍历map集合的key 51 for (int key : map.keySet()) { 52 // 如果优先队列的元素数是小于k的,此时还没有存够k个元素,直接进行入队 53 if (priorityQueue.size() < k) { 54 // 直接对于key进行入队操作 55 priorityQueue.add(key); 56 57 58 // 队首的元素就是对于优先队列来说,优先级最高的那个元素, 59 // 在这里的优先级定义下,优先级最高的就是频次最低的那个元素, 60 61 // 此时比较的就是key的频率 62 } else if (map.get(key) > map.get(priorityQueue.peek())) { 63 // 此时,优先队列里面已经有k个元素了,是我们当前看到的前k个频次最高的元素, 64 // 此时,新遍历的一个key,此时这个key的频次可能比当前这前k个频次最高的元素中 65 // 那个频次最小的那个元素的频次更高。这种情况下,此时应该将优先队列中频次最小的那个元素替换掉。 66 67 // 此时,让队首元素出队 68 priorityQueue.remove(); 69 // 然后让新的元素入队, 70 priorityQueue.add(key); 71 } 72 73 } 74 75 // 循环遍历结束,优先队列里面剩下的就是频次最高的前K个元素。 76 // 这样的算法,时间复杂度的O(nlogk) 77 List<Integer> list = new LinkedList<Integer>(); 78 while (!priorityQueue.isEmpty()) { 79 list.add(priorityQueue.remove()); 80 } 81 // 将符合条件的元素返回即可。 82 return list; 83 } 84 85 public static void main(String[] args) { 86 int[] nums = new int[]{1, 1, 1, 2, 2, 3}; 87 int k = 3; 88 HighFrequency2 highFrequency = new HighFrequency2(); 89 List<Integer> list = highFrequency.topKFrequent(nums, k); 90 for (int i = 0; i < list.size(); i++) { 91 System.out.println(list.get(i)); 92 } 93 } 94 95 }