操作系统如何管理运行的进程。
听音乐、下载文件和写文章。
每次新任务到达时都需要排序吗?
每次插入都要维护顺序吗?
堆非常适合快速获取最低/最高优先级元素。
常见的实现方式是二叉堆,它是一种基于完全二叉树的数据结构。
100
/ \
19 36
/ \ / \
17 3 25 1
2
/ \
19 3
/ \ / \
20 31 5 8
最大堆
最小堆
根节点是整个树中最小的节点
根节点是整个树中最大的节点
如果你要手动构建一个堆,那么你只需要一个数组。
插入操作(insert 或 offer)
删除操作
删除根节点操作(delete root 或 poll)
初始化操作
每次将元素插入到堆的末尾
然后进行上移操作
最多上移到根节点,操作次数为深度,即 log(N)
时间复杂度为 O(logN)
插入操作 -> 上移操作
90
/ \
70 50
/ \ / \
65 44 30 20
/ \ /
35 21 8
80
90
/ \
70 50
/ \ / \
65 44 30 20
/ \ / \
35 21 8 80
90
/ \
80 50
/ \ / \
65 70 30 20
/ \ / \
35 21 8 44
交换后,最多下移到叶子节点,操作次数为深度,即 log(N)。
时间复杂度为 O(logN)
删除根节点的操作的时间复杂度为 O(logN)。
时间复杂度为 O(logN),是相对于常数较为适中的复杂度。
90
/ \
70 50
/ \ / \
65 44 30 20
/ \ /
35 21 8
90
/ \
8 50
/ \ / \
65 44 30 20
/ \
35 21
90
/ \
65 50
/ \ / \
35 44 30 20
/ \
8 21
逐个插入元素,时间复杂度为O(NlogN)。
将每个元素逐一插入堆中的时间复杂度为O(NlogN)。
26, 45, 21, 37, 89, 12, 9
26
26
/
45
45
/
26
45
/ \
26 21
45
/ \
26 21
/
37
45
/ \
37 21
/
26
45
/ \
37 21
/ \
26 89
45
/ \
89 21
/ \
26 37
89
/ \
45 21
/ \
26 37
89
/ \
45 21
/ \ /
26 37 12
89
/ \
45 21
/ \ / \
26 37 12 9
逐层下沉,时间复杂度O(N)
逐层下沉,时间复杂度O(N)
45, 36, 18, 53, 72, 30, 48, 93, 15, 35
45
/ \
36 18
/ \ / \
53 72 30 48
/ \ /
93 15 35
45
/ \
36 18
/ \ / \
53 72 30 48
/ \ /
93 15 35
45
/ \
36 18
/ \ / \
53 72 30 48
/ \ /
93 15 35
45
/ \
36 18
/ \ / \
93 72 30 48
/ \ /
53 15 35
45
/ \
36 18
/ \ / \
93 72 30 48
/ \ /
53 15 35
45
/ \
36 48
/ \ / \
93 72 30 18
/ \ /
53 15 35
45
/ \
36 48
/ \ / \
93 72 30 18
/ \ /
53 15 35
45
/ \
93 48
/ \ / \
36 72 30 18
/ \ /
53 15 35
45
/ \
93 48
/ \ / \
53 72 30 18
/ \ /
36 15 35
45
/ \
93 48
/ \ / \
53 72 30 18
/ \ /
36 15 35
93
/ \
45 48
/ \ / \
53 72 30 18
/ \ /
36 15 35
93
/ \
72 48
/ \ / \
53 45 30 18
/ \ /
36 15 35
插入,时间复杂度O(logN)
初始化,时间复杂度O(N)
逐个插入元素,时间复杂度O(NlogN)
逐层下沉,时间复杂度O(N)
Comparator(比较器)
Comparable(可比较的)
class MyClass implements Comparable<MyClass> {
@override
public int compareTo(final MyClass o) {
return this.val - o.val; // increasing (minHeap)
}
}
PriorityQueue<> heap = new PriorityQueue<>(capacity);
class MyComparator implements Comparator<MyClass> {
public int compare(MyClass a, MyClass b) {
return a.val - b.val; // increasing (minHeap)
}
}
MyComparator myComparator = new MyComparator();
PriorityQueue<MyClass> heap = new PriorityQueue<MyClass>(capacity, myComparator);
PriorityQueue<MyClass> heap = new PriorityQueue<MyClass>(cap, new Comparator<MyClass>() {
public int compare(MyClass a, MyClass b) {
return a.val - b.val; // increasing (minHeap)
}
});
如果只能记住一个,那就始终使用Comparator。
如果需要大根堆
将(-num)添加到堆中
与Java类似。但是,C++中不使用Comparator或Comparable,而是需要重载运算符:>、<或编写一个compare方法。
堆适用于维护动态数据流,在需要保持最大值/最小值/某个位置值的同时,无需进行排序。
两个有序数组/链表
K个有序数组/链表
手动维护k个指针几乎不可能。
合并k个有序链表,返回一个排序后的链表,并分析描述其时间复杂度。
public ListNode merge(ListNode[] lists);
合并k个有序链表,返回一个排序后的链表,并分析描述其时间复杂度。
将所有链表的头节点插入小根堆(minHeap)中。
while (minHeap不为空)
root = minHeap.pop();
将root添加到结果链表中
将root.next插入到minHeap中。
返回结果链表
public ListNode mergeKLists(ListNode[] lists) {
if (lists == null || lists.length == 0)
return null;
Comparator<ListNode> comparator = new Comparator<ListNode> () {
public int compare(ListNode node1, ListNode node2) {
return node1.val - node2.val;
}
};
PriorityQueue<ListNode> minHeap =
new PriorityQueue<ListNode>(lists.length, comparator);
for (int i = 0; i < lists.length; i++) {
if (lists[i] != null) {
minHeap.add(lists[i]);
}
}
ListNode dummy = new ListNode(-1);
ListNode cur = dummy;
while (!minHeap.isEmpty()) {
cur.next = minHeap.poll();
cur = cur.next;
if (cur.next != null)
minHeap.add(cur.next);
}
return dummy.next;
}
这种方法可以用于外部排序(external merge sort)
但通常人们会使用锦标赛树(tournament tree)来执行外部排序,而不是使用堆。
10,2,8,5,20,30
7,9,0,-2,35,21
1,90,80,15,-1,6
2,5,8,10,20,30
-2,0,7,9,21,35
-1,1,6,15,80,90
-2,-1,0,1,2,5
6,7,8,9,10,15
20,21,30,35,80,90
给定一个数组nums,有一个大小为k的滑动窗口从数组的最左边移动到最右边。你只能看到窗口中的k个数字。每次滑动窗口向右移动一个位置。
例如,
给定nums = [1,3,-1,-3,5,3,6,7]和k = 3。
返回[3,3,5,5,6,7]。
注意:
可以假设k始终有效,即对于非空数组,有1≤k≤输入数组的大小。
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return nums;
}
int[] result = new int[nums.length - k + 1];
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(k,
new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return b - a;
}
});
for (int i = 0; i < nums.length; i++) {
if (i >= k) {
maxHeap.remove(nums[i-k]);
}
maxHeap.offer(nums[i]);
if (i >= k - 1) {
result[i - k + 1] = maxHeap.peek();
}
}
return result;
}
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return nums;
}
int[] result = new int[nums.length - k + 1];
PriorityQueue<Integer> minHeap = new PriorityQueue<>(k);
for (int i = 0; i < nums.length; i++) {
if (i >= k) {
minHeap.remove(-nums[i-k]);
}
minHeap.offer(-nums[i]);
if (i >= k - 1) {
result[i - k + 1] = -minHeap.peek();
}
}
return result;
}
时间复杂度是多少?
public int[] maxSlidingWindow(int[] nums, int k) {
if (nums == null || nums.length == 0) {
return nums;
}
Deque<Integer> deque = new LinkedList<>();
int[] result = new int[nums.length - k + 1];
for (int i = 0; i < nums.length; i++) {
if (!deque.isEmpty() && deque.peekFirst() == i - k) {
deque.poll();
}
while (!deque.isEmpty() && nums[deque.peekLast()] < nums[i]) {
deque.pollLast();
}
deque.offer(i);
if (i >= k - 1) {
result[i - k + 1] = nums[deque.peekFirst()];
}
}
return result;
}
一种更好的方法是使用双端队列(deque),可以实现O(n)的时间复杂度。
找到未排序数组中第k个最大元素。请注意,这是排序后的第k个最大元素,而不是第k个不同的元素。
例如,
给定[3,2,1,5,6,4]和k = 2,返回5。
注意:
可以假设k始终有效,即1≤k≤数组长度。
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
public int findKthLargest(int[] nums, int k) {
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return a - b;
}
};
PriorityQueue<Integer> minHeap = new PriorityQueue(k, comparator);
for (int i = 0; i < k; i++) {
minHeap.add(nums[i]);
}
for (int i = k; i < nums.length; i++) {
if (nums[i] >= minHeap.peek()) {
minHeap.poll();
minHeap.add(nums[i]);
}
}
return minHeap.poll();
}
Find the kth largest element in an unsorted array. Note that it is the kth largest element in the sorted order, not the kth distinct element.
中位数是有序整数列表中的中间值。如果列表的大小为偶数,则没有中间值。因此,中位数是中间两个值的平均值。
例如:
[2,3,4],中位数为3
[2,3],中位数为(2 + 3) / 2 = 2.5
设计一个数据结构,支持以下两个操作:
- void addNum(int num) - 从数据流中添加一个整数到数据结构中。
- double findMedian() - 返回到目前为止所有元素的中位数。
中位数是有序整数列表中的中间值。如果列表的大小为偶数,则没有中间值。因此,中位数是中间两个值的平均值。
class MedianFinder {
public void addNum(int num);
public double findMedian();
}
中位数是有序整数列表中的中间值。如果列表的大小为偶数,则没有中间值。因此,中位数是中间两个值的平均值。
minHeap
(较大的数字)
maxHeap
(较小的数字)
中位数
中位数是有序整数列表中的中间值。如果列表的大小为偶数,则没有中间值。因此,中位数是中间两个值的平均值。
minHeap
(较大的数字)
maxHeap
(较小的数字)
中位数
中位数是有序整数列表中的中间值。如果列表的大小为偶数,则没有中间值。因此,中位数是中间两个值的平均值。
minHeap
(较大的数字)
maxHeap
(较小的数字)
中位数
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Median
4
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
4
2
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Median
4
2
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Median
4
3
2
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
4
3
2
1
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Median
4
3
2
1
Median is the middle value in an ordered integer list. If the size of the list is even, there is no middle value. So the median is the mean of the two middle value.
Median
4
3
2
1
5
class MedianFinder {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>();
// Adds a number into the data structure.
public void addNum(int num) {
if (!minHeap.isEmpty() && num > minHeap.peek()) {
minHeap.offer(num);
} else {
maxHeap.offer(-num);
}
if (minHeap.size() - maxHeap.size() == 2) {
maxHeap.offer(-minHeap.poll());
} else if (maxHeap.size() - minHeap.size() == 2) {
minHeap.offer(-maxHeap.poll());
}
}
// Returns the median of current data stream
public double findMedian() {
if (minHeap.size() > maxHeap.size()) {
return minHeap.peek();
}
if (minHeap.size() < maxHeap.size()) {
return -maxHeap.peek();
}
return (double)(minHeap.peek() - maxHeap.peek()) / 2.0;
}
};
class MedianFinder {
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>();
// Adds a number into the data structure.
public void addNum(int num) {
minHeap.offer(num);
maxHeap.offer(-minHeap.poll());
if (maxHeap.size() - minHeap.size() > 1) {
minHeap.offer(-maxHeap.poll());
}
}
// Returns the median of current data stream
public double findMedian() {
if (minHeap.size() == maxHeap.size()) {
return (double)(minHeap.peek() - maxHeap.peek()) / 2.0;
}
return -maxHeap.peek();
}
};
给定m行,每行有n个数字,如果从每行中选择一个数字并将它们加起来,您可以得到n^m个不同的结果。请给出这些结果中最大的n个结果。输出也需要按降序排列。
示例:
m= 3, n = 3
5 8 7
2 9 5
0 2 3
结果:
[20, 19, 19]
提示: 20 = 8 + 9 + 3, 19 = 7 + 9 + 3, 19 = 8 + 9 + 2
当然,我们可以使用暴力方法
是否有更好的方法?
思考一下。我们每次添加一行中的数字,然后始终保持前n个。我们知道不在前n个数字中的数据永远不会成为最终结果的一部
因此,我们肯定会减少计算总和的时间。
如何维护前n个数字?我们使用小根堆
public List<Integer> topNumbers(int[][] numbers) {
int m = numbers.length;
int n = numbers[0].length;
PriorityQueue<Integer> minHeap = new PriorityQueue<>(n);
for (int i: numbers[0]) {
minHeap.add(i);
}
int[] list = new int[n];
for (int i = 1; i < m; i ++) {
for (int j = n - 1; j >= 0; j --) {
list[j] = minHeap.poll();
}
int[] cur = numbers[i];
Arrays.sort(cur, comparator);
int largest = cur[0];
for (int j = 0; j < n; j ++) {
minHeap.add(largest + list[j]);
}
for (int j = 1; j < n; j ++) {
for (int r = 0; r < n ; r ++) {
if (cur[j] + list[r] < minHeap.peek()) {
break;
}
minHeap.poll();
minHeap.add(cur[j] + list[r]);
}
}
}
List<Integer> result = new ArrayList<>();
result.addAll(minHeap);
Collections.sort(result, comparator);
return result;
}
Comparator<Integer> comparator = new Comparator<Integer>() {
public int compare(Integer a, Integer b) {
return b - a;
}
};
堆排序