有一类题,像有n个任务,n个会议,n颗树,要你按一定的顺序执行,使得总用时最少等,通常都需要想一个贪心策略,然后排序,再用优先队列逐一处理。
当然贪心是需要证明的,可以先找到一个序,然后证明交换任意两项不会更优。然而比赛的时候更多靠直觉。
LC 2136. 全部开花的最早一天
题意:有n颗植物,每颗植物需要先播种plantTime[i]天(不要求连续),再生长growTime[i]天,然后开花,求使所有植物开花的最短时间。
方法:
直接说结论:按growtime从大到小排序(感性认识就是,growTime越大提供的planTime的位置也越多,反正planTime也不要求连续,总能将growTime带来的空位填满)
比赛的时候我猜的growtime-plantTime,不一定最优,看来是完全不用考虑plantTime
画流水线图,与每行取max
class Solution {
public:
int earliestFullBloom(vector<int>& plantTime, vector<int>& growTime) {
vector<pair<int, int>> vec;
for(int i = 0;i < plantTime.size();i++) {
vec.push_back(make_pair(plantTime[i], growTime[i]));
}
sort(vec.begin(), vec.end(), [](pair<int, int>& a, pair<int, int>& b) {
return a.second > b.second;
});
int res = vec[0].first + vec[0].second;
int sum = 0;
for(int i = 0;i < vec.size();i++) {
sum += vec[i].first;
if(sum + vec[i].second > res) {
res = sum + vec[i].second;
}
}
return res;
}
};
LC 1834. 单线程 CPU
题意:n个任务,每次取可行任务中执行时间最短的执行
方法:先按开始时间排序,枚举时间,每次将能开始的加入队列中,并取队首执行
class Solution {
public:
struct Task {
int id, enqueueTime, processingTime;
Task(int id, int enqueueTime, int processingTime) :id(id), enqueueTime(enqueueTime), processingTime(processingTime) {}
};
vector<int> getOrder(vector<vector<int>>& tasks) {
/* 1. 按enqueueTime排序*/
vector<Task> taskList;
for(int i = 0;i < tasks.size();i++) {
taskList.push_back(Task(i, tasks[i][0], tasks[i][1]));
}
sort(taskList.begin(), taskList.end(), [](Task& a, Task& b) {
return a.enqueueTime < b.enqueueTime;
});
/*2. processingTime 短作业优先*/
auto cmp = [](const Task& a, const Task& b) { // 优先队列的比较函数和普通的相反
if(a.processingTime == b.processingTime) return a.id > b.id;
return a.processingTime > b.processingTime;
};
priority_queue<Task, vector<Task>, decltype(cmp)> pq(cmp);
int n = taskList.size();
long long curTime = 0; /* 3. 枚举时间 */
int idx = 0;
vector<int>ans;
int cnt = 0;
while(cnt < n) { // 统计弹出次数,直到n
// cout << curTime << endl;
while(idx < n && taskList[idx].enqueueTime <= curTime) pq.push(taskList[idx++]);
if(pq.empty()) curTime = taskList[idx].enqueueTime; // 为空,直接推进时间
else {
Task p = pq.top();pq.pop();
ans.push_back(p.id);
curTime += p.processingTime;
cnt++;
}
}
return ans;
}
};
LC 502. IPO
方法:和上面一题非常类似,也是先能放就放,然后取最大的执行,直到取到k个任务。
class Solution {
public:
typedef pair<int, int> PII;
int findMaximizedCapital(int k, int w, vector<int>& profits, vector<int>& capital) {
int n = profits.size();
vector<PII>tasks;
for(int i = 0;i < n;i++) tasks.push_back({capital[i], profits[i]});
sort(tasks.begin(), tasks.end());
priority_queue<int>pq;
int cnt = 0;
int i = 0, curW = w;
while(i < n || (!pq.empty())) {
while(i < n && tasks[i].first <= curW) pq.push(tasks[i++].second); // 能放一直放
if(!pq.empty()) {
auto p = pq.top();pq.pop(); // 取一个最大的
curW += p;
if((++cnt) >= k) break;
} else {
break;
}
}
return curW;
}
};
LC1383. 最大的团队表现值
方法:按工作效率从大到小排序,这样枚举到的每个效率值都是当前最小值。与此同时,将速度用一个大小为k的最小堆维护.
class Solution {
public:
typedef pair<int, int> PII;
const int mod = 1e9+7;
int maxPerformance(int n, vector<int>& speed, vector<int>& efficiency, int k) {
vector<PII>engineers;
for(int i = 0;i < n;i++) engineers.push_back({efficiency[i], speed[i]});
sort(engineers.begin(), engineers.end(), greater<PII>()); // efficiency从大到小
long long cnt = 0, ans = 0, sum = 0;
priority_queue<int, vector<int>, greater<int>>pq;
for(int i = 0;i < n;i++) {
// 维护一个大小为k的小根堆
if(cnt < k) {pq.push(engineers[i].second); cnt++; sum+=engineers[i].second;}
else {
if(pq.top() < engineers[i].second) {
sum -= pq.top();
pq.pop();
pq.push(engineers[i].second);
sum += engineers[i].second;
}
}
ans = max(ans, engineers[i].first * sum); // 相当于枚举了最小效率值
}
return (int)(ans % mod);
}
};
LC 1642. 可以到达的最远建筑
方法:从前往后,维护一个大小为ladders的堆,且砖块也不够用时返回
class Solution {
public:
int furthestBuilding(vector<int>& heights, int bricks, int ladders) {
priority_queue<int, vector<int>, greater<int>>pq;
int sum = 0, bigJump = 0;
int i = 0;
while(i < heights.size()-1) {
cout << bricks << endl;
int h = heights[i+1] - heights[i];
if(h <= 0) i++;
else {
pq.push(h); // 维护一个大小为ladders的堆
if(pq.size() > ladders) {bricks -= pq.top(); pq.pop();} // 大于梯子数就每次换个最小的出来
if(bricks < 0) return i;
i++;
}
}
return i;
}
};
LC 1005. K 次取反后最大化的数组和
方法:负数才要反转,维护k个最小的负数,如果k有剩余就去修改绝对值最小的那个数
class Solution {
public:
int largestSumAfterKNegations(vector<int>& nums, int k) {
int mymin = 100, sum = 0;
for(int num : nums) { // 找绝对值最小值
if(num < 0) mymin = min(mymin, -num);
else mymin = min(mymin, num);
sum += num;
}
priority_queue<int, vector<int>, greater<int>>pq;
int cur_sum = 0;
for(int num : nums) {
if(num < 0) {
if(pq.size() < k) {pq.push(-num); cur_sum += -num;}
else { //维护一个大小为k的最小堆
if(-num > pq.top()) {
cur_sum -= pq.top(); pq.pop();
pq.push(-num); cur_sum += -num;
}
}
}
}
// cout << sum << " " << cur_sum << endl;
int ans = sum + 2*cur_sum;
if((k - pq.size()) % 2 == 1) { // 剩余的k
ans -= 2*mymin;
}
return ans;
}
};