总有一场相遇是互相喜欢的
大家好,这里是新一,请多关照。在本篇博客中,新一将会为大家介绍数据结构与算法之堆面试高频题,堆在面试中也时不时会考到,在这里新一为大家整理堆面试高频题,为了方便大家理解,新一特地给大家附上了 源码和图片 便于大家理解,干货满满哟。(以下结果均在IDEA中编译)希望在方便自己复习的同时也能帮助到大家。
以下是我们的文章
一. Top-K问题
在面试中面试官可能会问我们以下问题:给你100w个数据,设计一个算法找到你前10个最大的元素
这里我们给出以下几种解决方法:
思路一:从小到大排序,再输出后10个元素,这虽然是一种解决方法,但面试官一般不会让你这样做,那样这个问题就没有意义了
思路二:将这一百万个元素整体建成一个大根堆,再依次出队10个元素即可,但占用的内存是不是有点多呢?
思路三:只建k个元素的小根堆,如果后续元素比堆顶元素大,那么先出队,然后再将这个元素入队,当遍历完整个数据时,最后小根堆上即为我们的top-k,显然是我们的思路三比较好
此外,还延伸出了以下类似于top-k的问题
求前k个最大元素,建一个小根堆
求前k个最小元素, 建一个大根堆
求第k大的元素,建一个小堆,堆顶元素就是第k大的元素
求第k小的元素,建一个大堆,堆顶元素就是第k小的元素
既然牵扯到最值问题,那么就肯定少不了对象的比较,如果我们要比较的数据类型是引用类型呢?比如说我们有如下对象:
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank; this.suit = suit;
}
//自定义类型一旦牵扯到比较,一定要重写comparable或者comparator接口
@Override
public String toString() {
return "Card{" +
"rank=" + rank +
", suit='" + suit + '\'' +
'}';
}
}
我们要根据扑克牌的数值进行比较,可以吗?
public static void main(String[] args) {
Card card1 = new Card(2,"♥");
Card card2 = new Card(1,"♥");
PriorityQueue<Card> priorityQueue = new PriorityQueue<>();
priorityQueue.offer(card1);
priorityQueue.offer(card2);
}
所以这里必须借助comparable或者comparator接口,下面我们用内部类来实现引用类型的比较:
public static void main(String[] args) {
Card card1 = new Card(2,"♥");
Card card2 = new Card(1,"♥");
//RankComparator rankComparator = new RankComparator();
//内部类 - 推荐
PriorityQueue<Card> priorityQueue = new PriorityQueue<>(new Comparator<Card>() {
@Override
public int compare(Card o1, Card o2) {
return o1.rank - o2.rank;
}
});
//lambda表达式 - 可读性差
//PriorityQueue priorityQueue = new PriorityQueue<>((x,y)->{return x.rank - y.rank;});
priorityQueue.offer(card1);
priorityQueue.offer(card2);
System.out.println(priorityQueue);
}
public class TopK {
/**
* 求数组中前K个最小的元素
* @param array
* @param k
* @return
*/
public static int[] topk(int[] array ,int k){
//1.创建一个大小为k的大根堆
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1;
}
});
//2.遍历数组中当中的元素,前k个元素放到队列中
for (int i = 0; i < array.length; i++) {
if (maxHeap.size() < k){
maxHeap.offer(array[i]);
}else {
int top = maxHeap.peek();
if (top > array[i]){
maxHeap.poll();
maxHeap.offer(array[i]);//将新元素放入其中
}
}
}
int[] ret = new int[k];
for (int i = 0; i < k; i++) {
ret[i] = maxHeap.poll();
}
return ret;
}
public static void main(String[] args) {
int[] array = {18,21,8,10,34,21};
int[] ret = topk(array, 3);
System.out.println(Arrays.toString(ret));
}
}
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
List<List<Integer>> ans = new ArrayList<>();
PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (nums1[a[0]] + nums2[a[1]]) - (nums1[b[0]] + nums2[b[1]]));
for (int i = 0; i < Math.min(nums1.length, k); i++) {
pq.add(new int[]{i, 0});
}
while (k > 0 && !pq.isEmpty()) {
int[] idx = pq.poll();
ans.add(List.of(nums1[idx[0]], nums2[idx[1]]));
if (idx[1] + 1 < nums2.length) {
pq.add(new int[]{idx[0], idx[1] + 1});
}
k--;
}
return ans;
}
}
二. 堆排序
我们要对一个堆进行排序,即使堆底层的数组变得有序,我们要使大根堆变得有序,那么每次只需将堆顶元素跟最后一个元素交换即可,交换后再向下调整即可实现堆排序;
public class TestHeap {
public int[] elem;
int usedSize;
public TestHeap() {
this.elem = new int[10];
}
/**
* 向下调整
* @param parent 每棵树的根节点
* @param len 每棵树调整的结束位置
*/
public void shiftDown(int parent, int len) {
int child = 2 * parent + 1;
//数学公式推导 - 错位相减 - 时间复杂度O(n)
//1.循环依次修改位置
while (child < len) {//向下调整一直到根
if (child + 1 < len && elem[child] < elem[child + 1]){//判断是否已经是叶子结点或者当前堆已经为大根堆
child++;
}
if (elem[parent] < elem[child]){//交换
int ret = elem[parent];
elem[parent] = elem[child];
elem[child] = ret;
}else{
break;
}
parent = child;//向下调整具体操作
child = child * 2 + 1;
}
}
public void createHeap(int[] array){
for (int i = 0; i < array.length; i++) {
elem[i] = array[i];
usedSize++;
}
for (int parent = (usedSize - 2) / 2; parent >= 0; parent--) {
shiftDown(parent,usedSize);
}
}
private void shiftUp(int child){
int parent = (child - 1) / 2;
while (parent >= 0){
if (elem[parent] < elem[child]){
int ret = elem[parent];
elem[parent] = elem[child];
elem[child] = ret;
}else{
break;
}
child = parent;
parent = (parent - 1) / 2;
}
}
public void offer(int val){
if (isFull()){
elem = Arrays.copyOf(elem, 2 * elem.length);
}
elem[usedSize++] = val;
shiftUp(usedSize - 1);
}
public boolean isFull(){
return usedSize == elem.length;
}
public int poll(){
if (isEmpty()){
throw new RuntimeException("优先级队列为空!");
}
int ret = elem[0];
elem[0] = elem[usedSize - 1];
elem[usedSize - 1] = ret;
usedSize--;
shiftDown(0,usedSize);
return ret;
}
public boolean isEmpty() {
return usedSize == 0;
}
public void heapSort(){
for (int end = this.usedSize - 1 ; end > 0; end--) {
int ret = elem[0];
elem[0] = elem[end];
elem[end] = ret;
shiftDown(0, end);
}
}
}
家人们,学到这里我们的数据结构与算法中的堆面试高频题已经彻底弄懂啦,如果觉得新一讲得还清楚的话,可以点个赞支持一下哦,后续新一会持续更新JAVA的有关内容,学习永无止境,技术宅,拯救世界!