鏈接:https://leetcode.com/tag/heap/
【23】 Merge k Sorted Lists
【215】 Kth Largest Element in an Array (無序數組中最小/大的K個數)(2018年11月30日第一次復習)
給了一個無序數組,可能有重復數字,找到第 k 個最大的元素並且返回這個元素值。
題解:直接用直接用個堆保存數組中最大的 K 個數。時間復雜度是 O(NlogK)。
1 //時間復雜度是 O(NlogK), 用堆輔助。
2 class Solution { 3 public: 4 int findKthLargest(vector<int>& nums, int k) { 5 const int n = nums.size(); 6 priority_queue<int, vector<int>, greater<int>> pq; 7 for (int i = 0; i < n; ++i) { 8 if (pq.size() < k) { 9 pq.push(nums[i]); 10 } else { 11 if (pq.top() < nums[i]) { 12 pq.pop(); 13 pq.push(nums[i]); 14 } 15 } 16 } 17 return pq.top(); 18 } 19 };
本題可以有 O(N) 的解法,詳見《劍指offer》或者《程序員代碼面試指南》P336
做法是quickSelect。
【218】 The Skyline Problem(2019年2月5日,算法群打卡復習)
給了一個tuple list, 每個三元組 [a, b, h] 代表一個樓的x軸坐標是在[a, b],樓的高度是 h,返回所有樓輪廓的左上角坐標。
題解:用了一個 multiset 來存這個樓是進來還是出去,如果是進來,就存[a, h], 如果是出去,就存[b, -h]。 然后遍歷這個 multiset,如果這個樓是進來的話,就看當前高度它是不是最高的,如果是,那么這個點在答案中,如果這個樓是出去的話,先把這個高度刪除,然后看當前這個高度是不是比剩下所有樓都高,如果是,就把當前坐標和剩下的最高的樓組成的坐標加入答案中。
1 class Solution { 2 public: 3 vector<pair<int, int>> getSkyline(vector<vector<int>>& buildings) { 4 for (auto& b : buildings) { 5 record.insert(make_pair(b[0], b[2])); 6 record.insert(make_pair(b[1], -b[2])); 7 } 8 vector<pair<int, int>> ret; 9 for (auto& r : record) { 10 bool entry = r.second > 0 ? true : false; 11 int idx = r.first, h = abs(r.second); 12 // printf("idx = %d, h = %d, entry = %d\n", idx, h, entry); 13 if (entry) { 14 if (h > getMaxHeight()) { 15 ret.push_back(make_pair(idx, h)); 16 } 17 height.insert(h); 18 } else { 19 auto iter = height.find(h); 20 height.erase(iter); 21 if (h > getMaxHeight()) { 22 ret.push_back(make_pair(idx, getMaxHeight())); 23 } 24 } 25 } 26 return ret; 27 } 28 struct kcmp { 29 bool operator() (const pair<int, int>& p1, const pair<int, int>& p2) const { 30 if (p1.first == p2.first) { 31 return p1.second > p2.second; 32 } 33 return p1.first < p2.first; 34 } 35 }; 36 multiset<pair<int, int>, kcmp> record; 37 multiset<int> height; 38 int getMaxHeight() { 39 if (height.empty()) {return 0;} 40 return *height.rbegin(); 41 } 42 };
【239】 Sliding Window Maximum (2019年2月18日復習)(用deque做)
求一個滑動窗口的最大值。
題解:用deque做。單調隊列。一開始還想錯了,需要多跑幾個case驗證一下正確性。時間復雜度是O(N)
1 class Solution { 2 public: 3 // [1 3 -1] -3 5 3 6 7 4 // 0 1 2 5 vector<int> maxSlidingWindow(vector<int>& nums, int k) { 6 const int n = nums.size(); 7 deque<int> dq; 8 vector<int> res; 9 for (int i = 0; i < n; ++i) { 10 if (!dq.empty() && i - dq.front() >= k) { 11 dq.pop_front(); 12 } 13 while (!dq.empty() && nums[dq.back()] < nums[i]) { 14 dq.pop_back(); 15 } 16 dq.push_back(i); 17 if (i - k + 1 >= 0) { 18 res.push_back(nums[dq.front()]); 19 } 20 } 21 return res; 22 } 23 };
【253】 Meeting Rooms II
題意是252的升級版,給了一個數組,數組里面的每個元素代表一個會議的開始時間和結束時間,問想安排下所有的會議,至少需要多少個會議室。
題解:這個題目在 sort 的分類里面說過,鏈接:https://www.cnblogs.com/zhangwanying/p/9914941.html
【264】 Ugly Number II
返回第 N 個丑數,丑數的定義是因子只有2 ,3 ,5 的數。
題解:我們用三根指針指向ans中應該乘2, 3 ,5的位置,然后從中選出最小的元素。
1 class Solution { 2 public: 3 int nthUglyNumber(int n) { 4 vector<int> nums(n, INT_MAX); 5 nums[0] = 1; 6 int p1 = 0, p2 = 0, p3 = 0; 7 for (int i = 1; i < n; ++i) { 8 int minn = min(nums[p1] * 2, min(nums[p2] * 3, nums[p3] * 5)); 9 if (minn == nums[p1] * 2) {++p1;} 10 if (minn == nums[p2] * 3) {++p2;} 11 if (minn == nums[p3] * 5) {++p3;} 12 nums[i] = minn; 13 } 14 return nums[n-1]; 15 } 16 };
【295】 Find Median from Data Stream (2018年11月30日,堆的題目)
給了一個數據流,設計api接口,addNum() 用於接收一個數,findMedian() 用於返回數據流的中位數。返回數據流的中位數。
Example: addNum(1) addNum(2) findMedian() -> 1.5 addNum(3) findMedian() -> 2
題解:這題是今天看算法課看到的一個題,我們用一個大根堆存儲數據流比較小的一半數字,用一個小根堆存數據流的比較大的一半數字。(這兩個堆的元素個數要么相等,要么存小數的那個堆比存大數的那個堆多存一個數。)中位數如果兩個堆元素個數相差 1 ,那么就返回大根堆的堆頂。不然返回兩個堆頂的平均數。
1 class MedianFinder { 2 public: 3 /** initialize your data structure here. */ 4 MedianFinder() { 5 6 } 7 void addNum(int num) { 8 lowhalf.push(num); 9 int k = lowhalf.top(); lowhalf.pop(); 10 highhalf.push(k); 11 if (lowhalf.size() < highhalf.size()) { 12 k = highhalf.top(); highhalf.pop(); 13 lowhalf.push(k); 14 } 15 return; 16 } 17 double findMedian() { 18 if (lowhalf.size() == highhalf.size()) { 19 return (double)(lowhalf.top() + highhalf.top()) / 2; 20 } 21 return (double)lowhalf.top(); 22 } 23 priority_queue<int, vector<int>, greater<int>> lowhalf; //小根堆存大的那邊 24 priority_queue<int> highhalf; //大根堆存小的那邊 25 }; 26 27 /** 28 * Your MedianFinder object will be instantiated and called as such: 29 * MedianFinder obj = new MedianFinder(); 30 * obj.addNum(num); 31 * double param_2 = obj.findMedian(); 32 */
【313】 Super Ugly Number (2019年2月9日,heap復習)
給了一個prime的數組(數組中 m 個元素),返回第 n 個 ugly number,ugly number 的定義是所有的因子都必須是 prime 數組中的元素。
題解:解法同因子是 2,3,5 的丑數,我們用 m 個指針分別指向prime數組中數應該乘的位置。然后同ugly number那題一樣。時間復雜度是 O(N*M)
1 class Solution { 2 public: 3 int nthSuperUglyNumber(int n, vector<int>& primes) { 4 const int m = primes.size(); 5 sort(primes.begin(), primes.end()); 6 vector<int> ptr(m, 0); 7 vector<int> ans(n, INT_MAX); 8 ans[0] = 1; 9 for (int i = 1; i < n; ++i) { 10 int minn = INT_MAX; 11 for (int k = 0; k < m; ++k) { 12 minn = min(minn, ans[ptr[k]] * primes[k]); 13 } 14 for (int k = 0; k < m; ++k) { 15 if (minn == ans[ptr[k]] * primes[k]) { 16 ptr[k]++; 17 } 18 } 19 ans[i] = minn; 20 } 21 return ans[n-1]; 22 } 23 };
【347】 Top K Frequent Elements
【355】 Design Twitter (2018年12月3日,heap專題)
實現四個 api。
- postTweet(userId, tweetId): Compose a new tweet.
- getNewsFeed(userId): Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent.
- follow(followerId, followeeId): Follower follows a followee.
- unfollow(followerId, followeeId): Follower unfollows a followee.
Example: Twitter twitter = new Twitter(); // User 1 posts a new tweet (id = 5). twitter.postTweet(1, 5); // User 1's news feed should return a list with 1 tweet id -> [5]. twitter.getNewsFeed(1); // User 1 follows user 2. twitter.follow(1, 2); // User 2 posts a new tweet (id = 6). twitter.postTweet(2, 6); // User 1's news feed should return a list with 2 tweet ids -> [6, 5]. // Tweet id 6 should precede tweet id 5 because it is posted after tweet id 5. twitter.getNewsFeed(1); // User 1 unfollows user 2. twitter.unfollow(1, 2); // User 1's news feed should return a list with 1 tweet id -> [5], // since user 1 is no longer following user 2. twitter.getNewsFeed(1);
題解:用個 unordered_map 標記用戶關系,然后一個 vector 存儲 twitter post
1 class Twitter { 2 public: 3 /** Initialize your data structure here. */ 4 Twitter() { 5 6 } 7 8 /** Compose a new tweet. */ 9 void postTweet(int userId, int tweetId) { 10 twit.push_back(make_pair(userId, tweetId)); 11 } 12 13 /** Retrieve the 10 most recent tweet ids in the user's news feed. Each item in the news feed must be posted by users who the user followed or by the user herself. Tweets must be ordered from most recent to least recent. */ 14 vector<int> getNewsFeed(int userId) { 15 set<int> people = followRelation[userId]; 16 people.insert(userId); 17 vector<int> ret; 18 int idx = twit.size() - 1, cnt = 0;; 19 while (cnt < 10 && idx >= 0) { 20 auto p = twit[idx--]; 21 int uid = p.first, tid = p.second; 22 if (people.find(uid) != people.end()) { 23 ret.push_back(tid); 24 cnt++; 25 } 26 } 27 return ret; 28 } 29 30 /** Follower follows a followee. If the operation is invalid, it should be a no-op. */ 31 void follow(int followerId, int followeeId) { 32 followRelation[followerId].insert(followeeId); 33 } 34 35 /** Follower unfollows a followee. If the operation is invalid, it should be a no-op. */ 36 void unfollow(int followerId, int followeeId) { 37 if (followRelation.find(followerId) == followRelation.end()) { return; } 38 if (followRelation[followerId].find(followeeId) == followRelation[followerId].end()) {return;} 39 followRelation[followerId].erase(followeeId); 40 } 41 42 unordered_map<int, set<int>> followRelation; 43 vector<pair<int, int>> twit; // (user, id) 44 }; 45 46 /** 47 * Your Twitter object will be instantiated and called as such: 48 * Twitter obj = new Twitter(); 49 * obj.postTweet(userId,tweetId); 50 * vector<int> param_2 = obj.getNewsFeed(userId); 51 * obj.follow(followerId,followeeId); 52 * obj.unfollow(followerId,followeeId); 53 */
【358】 Rearrange String k Distance Apart
【373】 Find K Pairs with Smallest Sums (2018年12月1日)
給了兩個整數數組 nums1 和 nums2, 和一個整數 k,求 nums1 和 nums2 的笛卡爾積中(就是 nums1 中選擇一個數字 n1,nums2 中選擇一個數字 n2 組成pair),sum 最小的 k 個pair。
題解:本題就是自定義排序的大根堆,pair排序的規則是 sum 大的在堆頂。
1 class Solution { 2 public: 3 vector<pair<int, int>> kSmallestPairs(vector<int>& nums1, vector<int>& nums2, int k) { 4 if (nums1.empty() || nums2.empty()) {return vector<pair<int, int>>();} 5 priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> pq; 6 for (auto n1 : nums1) { 7 for (auto n2 : nums2) { 8 pq.push(make_pair(n1, n2)); 9 if (pq.size() > k) { pq.pop(); } 10 } 11 } 12 vector<pair<int, int>> ret; //可能nums1和nums2的組合的pair不夠 k 個,所以不能一開始初始化為 k 個結果。 13 while (!pq.empty()) { 14 ret.push_back(pq.top()); 15 pq.pop(); 16 } 17 return ret; 18 } 19 struct cmp{ 20 bool operator()(const pair<int, int>& p1, const pair<int, int>& p2) { 21 return p1.first + p1.second < p2.first + p2.second; 22 } 23 }; 24 };
【378】 Kth Smallest Element in a Sorted Matrix
【407】 Trapping Rain Water II
【451】 Sort Characters By Frequency
【502】 IPO (2019年2月9日,算法群打卡)
給了一個 n 個項目,每個項目都有一個能力值 capital[i] 和一個利益值 profits[i],一開始有一個初始的能力值 W,要求是從這 n 個項目中選取 k 個項目,每次選取的項目的能力值capital[i]必須小於等於當前的人的能力值。每次做完一個項目,這個項目的利益值就會加在現在的能力值上面。問做完 k 個項目之后,最后能力值最大是多少。
題解:題目不難,重點在於利用heap實現。我們想着把 capital 數組 和 profits 數組綜合成一個數組,然后給新的數組排序,排序的方式按照capital從小到大,然后 profits 隨意。
然后利用一個 heap, 每次選擇一個項目的時候,都把小於當前能力值的項目放進heap,然后從 heap 中彈出一個當前利益值最大的項目,作為當前項目的選擇。
時間復雜度是 nlog(n)
1 class Solution { 2 public: 3 int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) { 4 const int n = Profits.size(); 5 vector<pair<int, int>> project(n); 6 for (int i = 0; i < n; ++i) { 7 project[i] = make_pair(Capital[i], Profits[i]); 8 } 9 sort(project.begin(), project.end()); 10 priority_queue<pair<int, int>, vector<pair<int, int>>, cmp> pq; 11 int idx = 0; 12 for (int i = 0; i < k; ++i) { 13 while (idx < n && project[idx].first <= W) { 14 pq.push(project[idx]); 15 ++idx; 16 } 17 if (pq.empty()) { break; } 18 pair<int, int> p = pq.top(); 19 pq.pop(); 20 W += p.second; 21 } 22 return W; 23 } 24 struct cmp { 25 bool operator() (const pair<int, int>& p1, const pair<int, int>& p2) const { 26 return p1.second < p2.second; 27 } 28 }; 29 };
【659】 Split Array into Consecutive Subsequences
【692】 Top K Frequent Words (2018年11月30日)
給了一個string類型的數組 words,在給定整數k,請嚴格按照出現頻率順序打印出現次數前 k 名的字符串。(出現頻率相同的按照字典順序打印)
題解:用個 unordered_map 存儲每個單詞出現的次數,然后建立一個小根堆,里面存 k 個pair,(小根堆自定義排序class),然后遍歷一遍map,把最后剩下在堆里的 k 個pair的字符串提取出來。
1 class Solution { 2 public: 3 vector<string> topKFrequent(vector<string>& words, int k) { 4 const int n = words.size(); 5 unordered_map<string, int> mp; 6 for (auto w : words) { mp[w]++; } 7 8 priority_queue<pair<string, int>, vector<pair<string, int>>, cmp> pq; 9 for (auto ele : mp) { 10 pq.push(ele); 11 if (pq.size() > k) { pq.pop(); } 12 } 13 vector<string> ret(k, ""); 14 int idx = k-1; 15 while (!pq.empty()) { 16 auto p = pq.top(); 17 pq.pop(); 18 ret[idx--] = p.first; 19 } 20 return ret; 21 } 22 //自定義比較函數,為啥我想小的排前面要寫大於。。 23 struct cmp { 24 bool operator()(const pair<string, int> a, const pair<string, int>& b) { 25 if (a.second == b.second) { 26 return a.first < b.first; 27 } 28 return a.second > b.second; 29 } 30 }; 31 };
//自定義比較函數,為啥我想小的排前面要寫大於。。(優先隊列的自定義比較函數要搞明白,還是不懂啊)
【703】 Kth Largest Element in a Stream
給個數字流,總是返回最大的第K個元素
解法就是用一個只有K個元素的堆,維護這這些數字里面從最大的第K個到最大的元素。
[最小元素...第K大的元素..最大元素], 這個堆總維護后半段的K個
View Code
【719】 Find K-th Smallest Pair Distance
【743】 Network Delay Time (2018年12月4日)
一個社交網絡里面有 N 個結點,標號為 1 ~ N,給了一個 times 數組,里面的元素 t(u, v, w) 代表從 u結點 到 v結點需要 w 的時間,給了一個源點 K, 問從 K 開始傳播一條信息,到每個結點需要至少多少時間,如果從 K 到一個結點不可達,就返回 -1。
題解:floyed求最短路。
1 class Solution { 2 public: 3 int networkDelayTime(vector<vector<int>>& times, int N, int K) { 4 vector<vector<int>> g(N+1, vector<int>(N+1, INT_MAX)); 5 for (auto t : times) { 6 int u = t[0], v = t[1], cost = t[2]; 7 g[u][v] = cost; 8 } 9 //floyed 10 for (int f = 1; f <= N; ++f) { 11 for (int i = 1; i <= N; ++i) { 12 g[i][i] = 0; 13 for (int j = 1; j <= N; ++j) { 14 if (g[i][f] != INT_MAX && g[f][j] != INT_MAX) { 15 g[i][j] = min(g[i][f] + g[f][j], g[i][j]); 16 } 17 } 18 } 19 } 20 int ret = INT_MIN; 21 for (int i = 1; i <= N; ++i) { 22 if (i == K) {continue;} 23 if (g[K][i] == INT_MAX) { 24 ret = -1; 25 break; 26 } 27 ret = max(ret, g[K][i]); 28 } 29 return ret; 30 } 31 };
此外,我沒看出來跟 heap 有啥關系。
【759】 Employee Free Time
【767】 Reorganize String
【778】 Swim in Rising Water
【786】 K-th Smallest Prime Fraction
【787】 Cheapest Flights Within K Stops (2019年2月9日)
2019年4月6日更新。heap 是個好東西。
給定了一張有向圖,還有邊的權重,問最多經過K個結點的情況下,從 src 到 dst 的最小花費。
題解:可以用 dijkstra 的思路,只不過這個題多了一個條件是最多經過 K 個結點,那么我們就可以在放入heap的結點中,新增一個緯度,叫做經過的站數。
1 //dijkstra 的思路,加上一個K站的條件 2 class Solution { 3 public: 4 int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) { 5 vector<vector<int>> graph(n, vector<int>(n, -1)); 6 for (int u = 0; u < n; ++u) { graph[u][u] = 0; } 7 for (auto& f : flights) { 8 int u = f[0], v = f[1], w = f[2]; 9 graph[u][v] = w; 10 } 11 priority_queue<vector<int>, vector<vector<int>>, greater<vector<int>>> pq; //{cost, city, how many stops visited} 12 pq.push({0, src, 0}); 13 while (!pq.empty()) { 14 auto temp = pq.top(); pq.pop(); 15 int price = temp[0], city = temp[1], stops = temp[2]; 16 if (city == dst) {return price;} 17 for (int v = 0; v < n; ++v) { 18 if (graph[city][v] == -1 || city == v) {continue;} 19 if (stops <= K) { 20 pq.push({price + graph[city][v], v, stops + 1}); 21 } 22 } 23 } 24 return -1; 25 } 26 };
【818】 Race Car
【857】 Minimum Cost to Hire K Workers (2019年3月15日,google tag)
題目給了兩個規則讓我們設計一個花費最小的辦法雇佣K個工人。
- Every worker in the paid group should be paid in the ratio of their quality compared to other workers in the paid group. 必須按照unitWage最大的那個工人的unitWage來付所有人的錢。
- Every worker in the paid group must be paid at least their minimum wage expectation.
題解:我們考慮如果將wage[i]/quality[i]作為評價指標來進行排序意味着什么?unitWage從小到大排序。 (wisdompeak大佬的題解)
wage[i]/quality[i]最高的那位,意味着最不實惠的工人,它拉高了unitWage,使得其他工人都必須按照這個unitWage乘以各自的quality拿工資.但轉念一想,如果我們必須雇佣這個最不實惠的工人的話,那么剩下的工人該如何選擇呢?顯然我們只要選K-1個quality最低的工人,他們可以拉高那個"最不實惠工人"的quality比重,從而減少其他工人的quality比重,從而降低總工資.
我們再考慮,如果選擇了wage[i]/quality[i]第二高的那位,那么我們就在接下來的 N-2 個人里面選擇 K-1 個quality最底的工人即可.
由此貪心法的最優策略就出來了.實際操作中,我們根據 wage[i]/quality[i] 從低到高進行處理.
1 class Solution { 2 public: 3 double mincostToHireWorkers(vector<int>& quality, vector<int>& wage, int K) { 4 int n = quality.size(); 5 vector<pair<double, int>> workers(n); 6 for (int i = 0; i < n; ++i) { 7 workers[i] = {(double)wage[i]/quality[i], quality[i]}; 8 } 9 sort(workers.begin(), workers.end()); 10 int totQuality = 0; 11 double res = DBL_MAX; 12 priority_queue<int, vector<int>, less<int>> pq; 13 for (int i = 0; i < n; ++i) { 14 double unitWage = workers[i].first; 15 int qua = workers[i].second; 16 if (pq.size() == K) { 17 totQuality -= pq.top(); 18 pq.pop(); 19 } 20 totQuality += qua; 21 pq.push(qua); 22 if (pq.size() == K) { 23 res = min(res, unitWage * totQuality); 24 } 25 } 26 return res; 27 } 28 };
【864】 Shortest Path to Get All Keys
【871】 Minimum Number of Refueling Stops
【882】 Reachable Nodes In Subdivided Graph

