支持的操作:O(logn) Add / O(logn) Remove / O(1) Min or Max (n是堆中的元素的個數)
Min Heap/ Max Heap(不能同時求最大和最小)
目錄:
- 1.基礎知識 - 取最大/小值,插入,刪除
- 2.Heap的基本原理和具體實現
2.1 基本原理
2.2 具體實現
2.3 基本操作 - Heapify - 3.相關題目
3.1 ugly-number-ii 醜數(4 in lintcode)
3.2 top-k-largest-numbers 前k大的數(544 in lintcode)
3.3 top-k-largest-numbers-ii 前k大的數(545 in lintcode)
3.4 merge-k-sorted-lists 合併k個排序鏈表(104 in lintcode)
3.5 kth-largest-element-ii 第k大的數(606 in lintcode)
3.6 其他題目
1. 基礎知識
1.1結構 - 儘量均衡
1.2 樹高
n個節點,heap的樹高是logn。 但不能認爲二叉樹的樹高是logn
1.3 節點順序
MaxHeap:- 1.每層都滿的二叉樹;2.每個小三角,parent的value一定大於等於左右兒子的value。
注:不能確定左右子樹中的value值得大小,也就是說parent的值比左右兒子大,但左右孩子的大小不確定。
1.4 操作1: 取最大值最小值在heap[0],複雜度爲O(1)
1.5 操作2: 插入Add - 如下圖,小頂堆123插入0
1.6 操作3: 刪除 Remove - 如下 刪除
a. 用最底層最右邊的節點換掉堆頂的元素
b. 當父節點比左右孩子節點都大時,應該換左右孩子中較小的那個節點上來
c. 刪除非堆頂元素時,將堆尾的元素替換掉要刪除的元素
1.8 java中的優先隊列Priority Queue 和Heap是等價的。
支持:offer() \ pop() \ peek()
不支持: 取第k大。
Priority Queue 只有刪除堆頂元素時,時間複雜度是O(logn); 用pop(any)刪除任意元素時,時間複雜度是:O(n).
如果要用堆頂替換要刪除的元素,只能自己實現,或者用HashHeap。
java中有TreeMap來支持刪除任意元素,c++中是Set。**TreeMap是一棵 Balance的BST,是一棵紅黑樹,**並且可以同時知道最大值最小值
1.9 TreeMap & PQ
2. Heap的基本原理和具體實現
2.1 基本原理
2.2 具體實現
2.3 基本操作 - Heapify
3. 相關題目
3.1 ugly-number-ii 醜數(4 in lintcode)
1.題目
http://www.lintcode.com/zh-cn/problem/ugly-number-ii/
http://www.jiuzhang.com/solution/ugly-number-ii/
設計一個算法,找出只含素因子2,3,5 的第 n 小的數。
符合條件的數如:1, 2, 3, 4, 5, 6, 8, 9, 10, 12…
2.思路 & 代碼
O(n)的算法不好理解,一定要掌握下面這種O(nlogn)的方法
思路:從{1}開始,每次取最小的數拿出來,依次*2,3,5,將新得到的數重新放進去。取最小是爲了避免遺漏。第i輪一定拿到的是第i小的數。即每一輪,都取當前最小的數,很顯然,使用堆。還需要去重,因爲10=25=52,所以還需要hash表。
PriorityQueue + Hash表
c++優先隊列的使用:https://www.cnblogs.com/cielosun/p/5654595.html
代碼:
class Solution {
public:
/*
* @param n: An integer
* @return: the nth prime number as description.
*/
typedef long long LL;//定義long long 類型的別名
const int coeff[3]={2,3,5};//定義基礎數組
int nthUglyNumber(int n) {
priority_queue<LL,vector<LL>,greater<LL>> pq; //定義優先隊列這個是越小的整數優先級越大的優先隊列
set<LL> s;//定義集合 排重
pq.push(1);//進入隊列
s.insert(1);//插入集合
LL x;
for(int i=1;i<=n;i++){
x=pq.top();//獲取到隊列第一個元素(因爲是按最小優先的原則排序的)
pq.pop();//第一元素出隊列 所以 第1500個醜數就是pop1499後的那個元素
for(int j=0;j<3;j++){
LL x2=x*coeff[j];//x2是2 3 5的倍數
//判斷集合中有沒有x2 若沒有進入if中
if(!s.count(x2)){
s.insert(x2);//插入到s集合中
pq.push(x2);//進隊列
}
}
}
return x;
}
};
O(n)的解法:
class Solution {
public:
/*
* @param n: An integer
* @return: the nth prime number as description.
*/
int nthUglyNumber(int n) {
// write your code here
int* ugly = new int[n];
ugly[0] = 1;
int next = 1;
int *p2 = ugly;
int *p3 = ugly;
int *p5 = ugly;
while(next < n){
int m = min(min(*p2 *2, *p3*3), *p5 * 5);
ugly[next] = m;
while(*p2 * 2 <= ugly[next]){
p2++; // *p2++
}
while(*p3 * 3 <= ugly[next]){
p3++; // *p3++
}
while(*p5 * 5 <= ugly[next]){
p5++; // *p5++
}
next++;
}
int res = ugly[n-1];
delete [] ugly;
return res;
}
};
**p++、(p)++、++p、++p 的區別
int a[5]={1,2,3,4,5};
int *p = a;
*p++ 先取指針p指向的值(數組第一個元素1),再將指針p自增1;
cout << *p++; // 結果爲 1
cout <<(*p++); // 1
(*p)++ 先去指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2
cout << (*p)++; // 1
cout <<((*p)++) //2
*++p 先將指針p自增1(此時指向數組第二個元素),* 操作再取出該值
cout << *++p; // 2
cout <<(*++p) //2
++*p 先取指針p指向的值(數組第一個元素1),再將該值自增1(數組第一個元素變爲2)
cout <<++*p; // 2
cout <<(++*p) //2
注意,上面的每條cout輸出,要單獨輸出才能得到後面的結果。
3.2 top-k-largest-numbers 前k大的數(544 in lintcode)
1.題目
http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers/
http://www.jiuzhang.com/solution/top-k-largest-numbers/
在一個數組中找到前K大的數
樣例
給出 [3,10,1000,-99,4,100], k = 3.
返回 [1000, 100, 10]
2.思路 & 代碼
3.3 top-k-largest-numbers-ii 前k大的數(545 in lintcode)
1.題目
http://www.lintcode.com/zh-cn/problem/top-k-largest-numbers-ii/
http://www.jiuzhang.com/solution/top-k-largest-numbers-ii/
實現一個數據結構,提供下面兩個接口
1.add(number) 添加一個元素
2.topk() 返回前K大的數
樣例
s = new Solution(3);
>> create a new data structure.
s.add(3)
s.add(10)
s.topk()
>> return [10, 3]
s.add(1000)
s.add(-99)
s.topk()
>> return [1000, 10, 3]
s.add(4)
s.topk()
>> return [1000, 10, 4]
s.add(100)
s.topk()
>> return [1000, 100, 10]
2.思路 & 代碼
用MinHeap。維護大小爲k的小頂堆,想擠進前k,只需要擠掉第k名即可。
3.4 merge-k-sorted-lists 合併k個排序鏈表(104 in lintcode)
1.題目
http://www.lintcode.com/zh-cn/problem/merge-k-sorted-lists/
合併k個排序鏈表,並且返回合併後的排序鏈表。嘗試分析和描述其複雜度。
樣例
給出3個排序鏈表[2->4->null,null,-1->null],返回 -1->2->4->null
2.思路 & 代碼
一般這種題目有兩種做法。
第一種做法比較容易想到,就是有點類似於MergeSort的思路,就是分治法。先把k個list分成兩半,然後繼續劃分,知道剩下兩個list就合併起來,合併時會用到類似 Merge Two Sorted Lists 這道題的思路。 這種思路我們分析一下複雜度,如果有k個list,每個list最大長度是n,那麼我們就有分治思路的複雜度計算公式 T(k) = 2T(k/2)+O(n*k)。 其中T(k)表示k個list合併的時間複雜度,用主定理可以算出時間複雜度是O(nklogk)。分治法又分兩種實現方式,一種是遞歸,另一種是兩兩鏈表進行多輪合併。
分治法java可以過,c++超時。
第二種做法是運用堆,也就是我們所說的priority queue。我們可以考慮維護一個大小爲k的堆,先把每個list的第一個元素放入堆之中,然後每次從堆頂選取最小元素放入結果最後的list裏面,然後讀取該元素所在list的下一個元素放入堆中,重新維護好堆,然後重複這個過程。因爲每個鏈表是有序的,每次又是取當前k個元素中最小的,所以最後結果的list的元素是按從小到大順序排列的。這種方法的時間複雜度也是O(nklogk)。
/**
* Definition of ListNode
* class ListNode {
* public:
* int val;
* ListNode *next;
* ListNode(int val) {
* this->val = val;
* this->next = NULL;
* }
* }
*/
class Solution {
public:
/**
* @param lists: a list of ListNode
* @return: The head of one sorted list.
*/
ListNode *mergeKLists(vector<ListNode *> &lists) {
// write your code here
if(lists.size()==0){
return NULL;
}
while(lists.size()>1){
vector<ListNode*> new_lists;
for(int i=0;i+1<lists.size();++i){
ListNode* tmp = mergeTwoLists(lists[i], lists[i+1]);
new_lists.push_back(tmp);
}
if(lists.size()%2 == 1){
new_lists.push_back(lists[lists.size()-1]);
}
lists = new_lists;
}
return lists[0];
}
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
ListNode dummy(0);
ListNode* tail = &dummy;
while(l1 && l2){
if(l1->val < l2->val){
tail->next = l1;
l1 = l1->next;
}
else{
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
if(l1){
tail->next = l1;
}
if(l2){
tail->next = l2;
}
return dummy.next;
}
};
/**
* Definition of ListNode
* class ListNode {
* public:
* int val;
* ListNode *next;
* ListNode(int val) {
* this->val = val;
* this->next = NULL;
* }
* }
*/
struct compare{
bool operator()(const ListNode* A, const ListNode* B)const{
return A->val > B->val;
}
};
class Solution {
public:
/**
* @param lists: a list of ListNode
* @return: The head of one sorted list.
*/
ListNode *mergeKLists(vector<ListNode *> &lists) {
// write your code here
if(lists.size()==0){
return NULL;
}
priority_queue<ListNode*, vector<ListNode*>, compare> q;
for(int i=0;i<lists.size();++i){
if(lists[i])
q.push(lists[i]);
}
ListNode dummy(0);
ListNode* tail = &dummy;
while(!q.empty()){
ListNode* head = q.top();
q.pop();
tail->next = head;
tail = tail->next;
if(head->next){
q.push(head->next);
}
}
return dummy.next;
}
};
3.5 kth-largest-element-ii 第k大的數(606 in lintcode)
1.題目
http://www.lintcode.com/zh-cn/problem/merge-k-sorted-lists/
2.思路 & 代碼
使用heap和quick-select的對比:
struct compare{
bool operator()(const int &a, const int &b)const{
return a > b;
}
};
class Solution {
public:
/*
* @param nums: an integer unsorted array
* @param k: an integer from 1 to n
* @return: the kth largest element
*/
int kthLargestElement2(vector<int> &nums, int k) {
//write your code here
priority_queue<int, vector<int>, compare> pq;
for(int i=0;i<k;++i){
pq.push(nums[i]);
}
for(int i=k;i<nums.size();++i){
if(nums[i] > pq.top()){
pq.pop();
pq.push(nums[i]);
}
}
return pq.top();
}
};
3.6 其他題目
http://www.lintcode.com/en/problem/high-five/(A)
http://www.lintcode.com/en/problem/k-closest-points/(L, A, F)
http://www.lintcode.com/problem/merge-k-sorted-arrays/
http://www.lintcode.com/problem/data-stream-median/
http://www.lintcode.com/problem/top-k-largest-numbers/
http://www.lintcode.com/problem/kth-smallest-number-in-sorted-matrix/
Hive Five 優秀成績 - heap、amazon
http://www.lintcode.com/en/problem/high-five/(A)
如果用堆,建堆時間 O(5log5), 後續調整堆的總時間:O(nlog5)
不用堆,只是用一個數組,則總時間是:O(n * 5)
時間在同一數量級的情況下,採用後者,代碼更簡單。
Top k largest numbers 前k大的數 - heap、priority_queue
http://www.lintcode.com/problem/top-k-largest-numbers/
兩種思路:
思路1:quickSort的思路,只進行quiksort的前k輪
思路2:使用小頂堆
struct compare{
bool operator()(const int &a, const int &b)const{
return a > b;
}
};
class Solution {
public:
/*
* @param nums: an integer array
* @param k: An integer
* @return: the top k largest numbers in array
*/
vector<int> topk(vector<int> nums, int k) {
// write your code here
priority_queue<int, vector<int>, compare> pq;
for(int i=0;i<k;++i){
pq.push(nums[i]);
}
for(int i=k;i<nums.size();++i){
if(nums[i] > pq.top()){
pq.pop();
pq.push(nums[i]);
}
}
vector<int> res;
while(!pq.empty()){
res.push_back(pq.top());
pq.pop();
}
reverse(res.begin(),res.end());
return res;
}
};
class Solution {
public:
/*
* @param nums: an integer array
* @param k: An integer
* @return: the top k largest numbers in array
*/
vector<int> topk(vector<int> nums, int k) {
// write your code here
if(nums.size() == 0){
return nums;
}
quicksort(nums, 0, nums.size()-1, k);
vector<int> res(k,0);
for(int i=0;i<k;++i){
res[i] = nums[i];
}
return res;
}
void quicksort(vector<int> &nums, int start, int end, int k){
if(start >= k){
return;
}
if(start >= end){
return;
}
int left = start, right = end, pivot = nums[(left+right)/2];
while(left <= right){
while(left<=right && nums[left] > pivot){
left++;
}
while(left<=right && nums[right] < pivot){
right--;
}
if(left <= right){
int tmp = nums[left];
nums[left] = nums[right];
nums[right] = tmp;
left++;
right--;
}
}
quicksort(nums, start, right, k);
quicksort(nums, left, end, k);
}
};
K Closest Points K個最近的點 - heap、 amazon、 linkedin
http://www.lintcode.com/en/problem/k-closest-points/
大頂堆,每次去掉最大的,始終維持最小的五個
關於c++中優先隊列的使用
struct compare{
bool operator()(const Point& a, const Point& b)const{
int diff = getDis(a,global_origin) - getDis(b, global_origin);
if(!diff){
diff = a.x-b.x;
}
if(!diff){
diff = a.y - b.y;
}
return diff<0;
}
};
priority_queue<Point, vector, compare> pq; //該優先隊列按從大到小排列
compare函數的定義中,如果return的地方是小於號,則是按從大到小排列,如果是>,則是按從小到大排列。默認情況下是從大到小排列。