Learning card
queue的一種,但是推進去的資料會照大小排序。
// 從大排到小
priority_queue<int> pq;
// 從小排到大
priority_queue<int, vector<int>, greater<int>> pq;
// 自定義compare function
auto cmp = [](int& a, int& b) {
return a > b; // 從小排到大和sort的compare相反 // 因為當cmp成立數值a會往下排列
// 所以最小的會在最上面
};
priority_queue<int, vector<int>, decltype(cmp)> pq(cmp);
time complexity如下:
其中N為在container中的element數量。
size/empty/top | push/pop | |
---|---|---|
priority_queue | \(O(1)\) | \(O(logN)\) |
因為push和pop的效率和N息息相關,所以裡面的數量越少越好。
因為使用priority_queue必須有額外的空間來儲存,可以使用vector本身來當成queue,則space complexity可以變成\(O(1)\)。因為vector操作back比較方便,只需要\(O(1)\),所以pop_heap和push_heap也是利用back來操作。
vector<int> nums;
make_heap(nums.begin(), nums.end()); // make max-heap
// pop
pop_heap(nums.begin(), nums.end()); // 把top element推到back
nums.pop_back(); // 如果不需要必須把最後一個丟掉
// push
nums.push_back(n); // 把要push進去的element放到最後
push_heap(nums.begin(), nums.end());
使用make_heap的題目:
列出數列中nums出現頻率最多的k個數字。
把所有的數列都推進去,所以最後取出質料的time complexity為\(O(KlogN)\) 因為使用max-heap所以答案就是最上面的k個。
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freq;
for(auto& n : nums)
freq[n]++;
priority_queue<pair<int, int>> pq;
for(auto it : freq)
pq.push({it.second, it.first});
vector<int> rtn;
for(int i = 0; i < k; ++i) {
rtn.push_back(pq.top().second);pq.pop();
}
return rtn;
}
另外可以使用min-heap,這樣priority_queue裡面只需要保留k個數值。這樣time complexity可以降為\(O(NlogK)\)。
vector<int> topKFrequent(vector<int>& nums, int k) {
unordered_map<int, int> freq;
for(auto& n : nums)
freq[n]++;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
for(auto it : freq) {
pq.push({it.second, it.first});
if(pq.size() > k) pq.pop();
}
vector<int> rtn;
while(!pq.empty()) {
rtn.push_back(pq.top().second); pq.pop();
}
return rtn;
}
max-heap | min-heap | |
---|---|---|
統計出現頻率 | \(O(NlogN)\) | \(O(NlogN)\) |
資料推入 | \(O(NlogN)\) | \(O(NlogK)\) |
資料取出 | \(O(K)\) | \(O(K)\) |
給你一連串的meeting room intervals。試問需要多少的meeting room才可以符合這些intervals。
1.只在乎最快結束的meeting room時間,如果連最早結束的時間都比intervals[i][0]還晚,則表示需要另一間meeting room。
2.如果連最早結束的時間都比intervals[i][0]還早,則pop這個時間,因為這間meeting room已經使用完了。可以排入下一個。
int minMeetingRooms(vector<vector<int>>& intervals) {
sort(intervals.begin(), intervals.end());
priority_queue<int, vector<int>, greater<int>> pq;
// 使用min-heap來看最早結束的meeting root是那一個
for(auto& it : intervals) {
// pq.top() <= it[0],表示會議已經結束,可以使用這間meeting room
// 把目前的interval排入,等於更新結束時間。pop() then push
// 不然所有的meeting room都還沒結束,推進pq視為一個新的
if(!pq.empty() && pq.top() <= it[0]) pq.pop();
pq.push(it[1]);
}
return pq.size();
}
sort() | push/pop | |
---|---|---|
Time complexity | \(O(NlogN)\) | \(O(N*2logN)\) |
int furthestBuilding(vector<int>& heights, int bricks, int ladders) {
auto sz = heights.size();
// 因為ladders可以爬無限多階,所以留給高度差最大的使用
priority_queue<int, vector<int>, greater<int>> l;
int idx = 0;
for(idx = 0; idx < sz; ++idx) {
if(idx == sz - 1) break;
int diff = heights[idx + 1] - heights[idx];
if(diff <= 0) continue;
l.push(diff);
if(l.size() > ladders) {
bricks -= l.top();
l.pop();
if(bricks < 0) // 因為bricks有變動才需要check
break;
}
}
return idx;
}
2022/06/21
- 解法和之前一樣用min-heap來儲存最大值,用來使用ladders
- 站在目前的idx往前看,如果bricks >= 0 或是pq.size() < ladders,就繼續往前爬。
int furthestBuilding(vector<int>& heights, int bricks, int ladders) {
int sz = heights.size();
if(sz == 1) return 0;
priority_queue<int, vector<int>, greater<int>> pq;
int idx = 0;
for(; idx < sz && bricks >= 0; ++idx) {
if(idx == sz - 1) break; // 因為沒有idx+1
int diff = heights[idx + 1] - heights[idx];
if(diff <= 0) continue;
pq.push(diff);
if(pq.size() > ladders) {
bricks -= pq.top();
pq.pop();
}
if(bricks < 0) break;
}
return idx;
}
time | space | |
---|---|---|
complexity | \(O(NlogL)\) | \(O(L)\) |
其中N為 size of heights,L 為ladders。
1.使用自己定義的compare function。
2.compare function如果return true,就是把前面的element往下移動。
vector<int> kWeakestRows(vector<vector<int>>& mat, int k) {
auto cmp = [](pair<int, int>& a, pair<int, int>& b) {
if(a.first > b.first) return true;
else if(a.first == b.first) return a.second > b.second;
else return false;
};
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> pq(cmp);
auto nr = mat.size();
auto nc = mat[0].size();
for(int i = 0; i < nr; ++i) {
int j = 0;
while(j < nc && mat[i][j] == 1) ++j;
pq.push({j, i});
}
vector<int> rtn;
while(k) {
rtn.push_back(pq.top().second);
pq.pop();
k--;
}
return rtn;
}
class MedianFinder {
priority_queue<int> low; // top 大
priority_queue<int, vector<int>, greater<int>> high; // top 小
public:
MedianFinder() {}
void addNum(int num) {
low.push(num);
high.push(low.top());
low.pop();
if(high.size() > low.size()) {
low.push(high.top());
high.pop();
}
}
double findMedian() {
return low.size() == high.size() ?
(low.top() + high.top()) / 2.0 :
low.top();
}
};
- 如果沒任何限制,當然是全部修
- Greedy,因為題目是求最多的修課數,所以修的課duration越小越有機會修越多。(從小的開始修,刪掉目前修過最大的)
- 使用max-heap來記錄目前修課中最大的duration。
- 如果遇到超過lastday就退選最大的duration,來達到最多的修課數。
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(), courses.end(), [&](vector<int>& a, vector<int>& b){
return a[1] < b[1];
});
priority_queue<int> pq;
int start = 0;
for(auto& c : courses) {
int duration = c[0];
int lastday = c[1];
start += duration;
// 先push進去,這樣取到的duration一定大於或等於目前的值
pq.push(duration);
if(start > lastday) {
start -= pq.top();
pq.pop();
}
// 把最大的減掉就一定會小於等於lastday?
// 有沒有可能目前的cur >= lastdya?
}
return pq.size();
}
給你兩個vector<int>,數值只有16,每次操作可以從任意element(16)跳到1~6,求最少次操作,可以把兩個vector的sum達到一樣。
- 請先參考 Greedy的解答
- 使用priority_queue,一個用min-heap,一個用max-heap,然後判斷要先取哪一個。
int minOperations(vector<int>& n1, vector<int>& n2) {
if (n2.size() * 6 < n1.size() || n1.size() * 6 < n2.size())
return -1;
int sum1 = accumulate(begin(n1), end(n1), 0), sum2 = accumulate(begin(n2), end(n2), 0);
if (sum1 > sum2)
swap(n1, n2);
// 因為n1的sum比較小使用min-heap,要往上跳
priority_queue <int, vector<int>, greater<int>> q1(begin(n1), end(n1));
// 因為n2的sum比較大使用max-heap,要往下跳
priority_queue<int> q2(begin(n2), end(n2));
int cnt = 0, diff = abs(sum1 - sum2);
while (diff > 0) {
++cnt;
// 取q2的條件:
// 1. q1是empty
// 2. q2不是empty,且往下跳的幅度(q2.top() - 1)
// 比往上跳的幅度(6 - q1.top())還大
if (q1.empty() || (!q2.empty() && q2.top() - 1 > 6 - q1.top())) {
diff -= q2.top() - 1;
q2.pop();
} else {
diff -= 6 - q1.top();
q1.pop();
}
}
return cnt;
}
但是這種解法的space complexity為\(O(N+M)\),因為需要兩個priority_queue。所以可以使用make_heap來把space complexity變成\(O(1)\)
int minOperations(vector<int>& n1, vector<int>& n2) {
if (n2.size() * 6 < n1.size() || n1.size() * 6 < n2.size())
return -1;
int sum1 = accumulate(begin(n1), end(n1), 0), sum2 = accumulate(begin(n2), end(n2), 0);
if (sum1 > sum2)
swap(n1, n2);
// min-heap
make_heap(begin(n1), end(n1), greater<int>());
// max-heap
make_heap(begin(n2), end(n2));
int cnt = 0, diff = abs(sum1 - sum2);
while (diff > 0) {
++cnt;
if (n1.empty() || (!n2.empty() && n2.front() - 1 > 6 - n1.front())) {
diff -= n2.front() - 1;
pop_heap(begin(n2), end(n2)); n2.pop_back();
} else {
diff -= 6 - n1.front();
pop_heap(begin(n1), end(n1), greater<int>()); n1.pop_back();
}
}
return cnt;
}
給你vector<int> piles,每次操作可以選一個pile然後減少floor(piles[i] / 2)的石頭,最多可以操作K次,試問最少剩下多少石頭。
- Greedy點,每次操作都選最大堆,這樣可以減掉最多石頭。
- 選最大堆–> priority_queue
int minStoneSum(vector<int>& piles, int k) {
make_heap(piles.begin(), piles.end());
while(k--) {
piles.front() -= piles.front() / 2;
pop_heap(piles.begin(), piles.end()); // move the top to back
push_heap(piles.begin(), piles.end()); // push the back element to pq
}
return accumulate(piles.begin(), piles.end(), 0);
}
給你一個string s,重新排列讓新的string每個char只少都有k個distance。
- 使用priority_queue來拿最多的char(Greedy)。
- 使用queue來讓使用過的char排隊,當排了k次,就又可以回到priority_queue。
- 把count == 0 也往queue裡面塞,這樣可以把剩下的全部擠出queue。
- 從queue拿出來的資料中,count==0表示queue內的資料已經全部取完。
class Solution {
public:
string rearrangeString(string s, int k) {
using pii = pair<int, int>; //count, char
int cnt[26] = {0};
priority_queue<pii> pq;
queue<pii> q; // wait here for k time
for (auto ch : s) cnt[ch - 'a']++;
for (auto i = 0; i < 26; ++i)
if (cnt[i] != 0) pq.push({cnt[i], i});
string res = "";
while (!pq.empty()) {
auto p = pq.top(); pq.pop();
res += 'a' + p.second;
q.push({p.first - 1, p.second}); /* 把count = 0 繼續往裡面塞,把剩下的都擠出來 */
// 使用 >= 使因為 當k == 0,
// 就需要使用大於的情況。
if (q.size() >= k) {
auto p = q.front(); q.pop();
// 如果再次遇到count==0,表示queue都清光了
if (p.first) // count != 0
pq.push({p.first, p.second});
}
}
return res.size() == s.size() ? res : "";
}
};
給你兩個vector<int> nums1和nums2。任取k個組合,使得score最大。score定義,
\(sum(num1[i0] + nums1[i1] + ... + nums1[ik-1]) * min(num2[i0] + nums2[i1] + ... + nums2[ik-1])\)
- 2023/05/24,一開始我也是不知所措了一陣子。
- 因為是取min(nums2[]),所以想到了用nums2來排序,這樣每次的min都會固定。
- 當取的數字大於k,就需要踢掉一個,Greedy的想法當然是踢掉最小的那個,所以使用minheap。
class Solution {
public:
long long maxScore(vector<int>& nums1, vector<int>& nums2, int k) {
vector<vector<int>> v;
int sz = nums1.size();
for(int i = 0; i < sz; ++i) // O(N)
v.push_back({nums2[i], nums1[i]});
sort(v.begin(), v.end(), [](vector<int>& a, vector<int>& b){ // O(NlogN)
return a[0] > b[0];
});
priority_queue<int, vector<int>, greater<int>> pq; // minheap
long long sum{}, min, ans{};
for(int i = 0; i < sz; ++i) { // O(NlogK)
sum += v[i][1];
min = v[i][0];
pq.push(v[i][1]);
if(pq.size() > k) {
sum -= pq.top();
pq.pop();
}
if(i >= k - 1) ans = max(ans, (long long)sum * min);
}
// time complexity : O(N + NlogN + NlogK) = O(NlogN)
// space complexity : O(N + K)
return ans;
}
};
/*
{2, 1}{1, 3}{3, 3}{4, 2} -> sort by nums2
{4, 2}{3, 3}{2, 1}{1, 3}
* 取到這個的時候,因為要納入3,
所以踢掉最小的1
*/
leetcode
刷題