知识点总结
如果已经了解以下知识点,则没有必要继续阅读该文章
- 堆排序的原理与复杂度:建堆O(n)+堆排序O(nlogn),空间复杂度O(1)
- 堆排序的特点:永远保持堆顶的最大(小)值特性
- 优先队列基于堆排序实现.
- 堆排序对应的笔试题:已知关键字序列5,8,12,19,28,20,15,22是小根堆(最小堆),插入关键字3,调整后得到的小根堆是()?
- 3,5,12,8,28,20,15,22,19
- 优先队列对应的面试编程题
- 从两个有序数组中分别选择一个元素并求和,求第K大的数组和.
- 滑动窗口最大值
堆排序原理
如图所示,分别是建堆和堆排序两步操作.
建堆
- 将数组以层序遍历的顺序写入满二叉树
- 从最后一个非叶子节点从右到左,从下到上开始调整顺序,每次比较左右子树,选最值作为父节点.
堆排序
- 输出根节点
- 最后一个叶子节点移动到根节点
- 从上到下递归调整堆,选最值作为父节点
- 重复123,直到输出所有节点
优先队列原理和使用
原理
每个元素有优先级(有序)
- 底层用堆实现,因此始终保持的是根节点的最值性质,子节点无全局序.
- push就是进堆并调整堆,poll就是出堆并调整堆.
基本使用(java)
PriorityQueue<Integer> smallPQ = new PriorityQueue<>();//最小堆
pq.add(3);
pq.add(1);
pq.add(2);
int head = pq.peek(); // head = 1, 取根节点但不取出
int head = pq.poll(); // head = 1, 取根节点并取出
PriorityQueue<Integer> bigPQ = new PriorityQueue<>(Comparator.reverseOrder());//最大堆
//结构体优先队列
class Node{
int i;
int j;
long val;
public Node(int _i,int _j,long _val){
i=_i;
j=_j;
val=_val;
}
}
PriorityQueue<Node> pq = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return (int)(o1.val - o2.val); //从小到大
}
});
笔面试题例子
笔试题
面试题
第K小的数组和
有两个大小都是k的数组A,B,它们元素的按非递减有序排列,找出这样的第k个最小的(ai + bj) ,其中 0<= i,j < k,要求算法的时间复杂度和空间复杂度尽量低。
给出 A = [1,7,11], B = [2,4,6]
当 k = 3, 返回 7.
当 k = 4, 返回 9.
当 k = 8, 返回 15.
解析
以两个数组作为行和列,可以获得数组对应位置求和形成的矩阵,该矩阵特性: 每行小到大排序,每列小到大排序
| 1 | 7 | 11 | |
|---|---|---|---|
| 2 | 3 | 9 | 13 |
| 4 | 5 | 11 | 15 |
| 6 | 7 | 13 | 17 |
- 维护一个优先队列(堆),先将arr[0][0]入堆,并用二维数组标志哪些元素已经入过堆
- 循环k次,每次将根节点抛出,并将节点的右元素arr[i][j+1]和下元素arr[i+1][j]进堆,此时就能保证下一个比根节点大的节点一定在堆里,因此局部最值转换成了全局最值.
代码(java)
import java.util.Comparator;
import java.util.PriorityQueue;
class Solution {
class Node{
int i;
int j;
long val;
public Node(int _i,int _j,long _val){
i=_i;
j=_j;
val=_val;
}
}
public long kthSmallestProduct(int[] nums1, int[] nums2, long k) {
PriorityQueue<Node> queue = new PriorityQueue<>(new Comparator<Node>() {
@Override
public int compare(Node o1, Node o2) {
return (int)(o1.val - o2.val);
}
});
int[][] visit = new int[nums1.length][nums2.length];
queue.add(new Node(0,0,nums1[0]*nums2[0]));
visit[0][0] = 1;
int cnt=0;
long ans = 0;
while(cnt<k){
Node cur = queue.poll();
ans = cur.val;
if(cur.j+1<nums2.length&&visit[cur.i][cur.j+1]!=1){
queue.add(new Node(cur.i,cur.j+1, nums1[cur.i]+nums2[cur.j+1]));
visit[cur.i][cur.j+1]=1;
}
if(cur.i+1<nums1.length&&visit[cur.i+1][cur.j]!=1){
queue.add(new Node(cur.i+1,cur.j, nums1[cur.i+1]+nums2[cur.j]));
visit[cur.i+1][cur.j]=1;
}
cnt++;
}
return ans;
}
}
滑动窗口最大值
解析
“获取滑动窗口中的最大值”,对应堆排序的局部最大值特性,因此可以用优先队列解决,由于某一时刻的滑动窗口最大值也可能是下一个滑动窗口的最大值,因此将下标也存储进去,以下标来判断是否将根节点抛出。
代码实现
class Solution {
public int[] maxSlidingWindow(int[] nums, int k) {
if(k==1){
return nums;
}
PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o2[0] - o1[0];
}
});
int[] ans = new int[nums.length-k+1];
for (int i = 0; i < k; i++) {
queue.add(new int[]{nums[i],i});
}
ans[0] = queue.peek()[0];
for (int i = k; i < nums.length; i++) {
while(queue.peek()[1]<=i-k){
queue.poll();
}
queue.add(new int[]{nums[i],i});
ans[i-k+1] = queue.peek()[0];
}
return ans;
}
}