五月集训(第21天)—堆
堆
1. 2099. 找到和最大的长度为 K 的子序列
思路:
将每个元素的值和下标打包,利用堆进行排序,取出其中最大的K个元素,再按照下标恢复原来的顺序即可。详细实现思路见代码注释。
class Solution {
class Pair {
public:
int val;
int idx;
Pair() {}
Pair(int v, int i) : val(v), idx(i) {}
// 重载运算符
bool operator < (const Pair& o) const {
return val < o.val;
}
};
public:
vector<int> maxSubsequence(vector<int>& nums, int k) {
priority_queue<Pair> q;
int n = nums.size();
vector<Pair> t;
// 利用堆排序,为了恢复原顺序,带着下标排序
for (int i = 0; i < n; i++) {
q.push( Pair(nums[i], i) );
}
// 将最大的 k 个值保留作为答案
while (k--) {
t.push_back( q.top() );
q.pop();
}
// 恢复答案的下标顺序
sort(t.begin(), t.end(), [&](const Pair& a, const Pair& b) {
return a.idx < b.idx;
});
// 转为答案要求的格式
vector<int> ret;
k = t.size();
for (int i = 0; i < k; i++) ret.push_back(t[i].val);
return ret;
}
};
2. 1792. 最大平均通过率
思路:
主要是要想明白把extraStudents安排到那个班级,要使平均通过率更大,每次安排都要让有更大进步空间的班级提升通过率,这样可以使通过率提高的更多。$(x + 1) / (y + 1) - x / y$ 作为判断依据
class Solution {
struct klass {
int up;
int dn;
klass(int u, int d) : up(u), dn(d) {}
// 重载运算的依据是,差分值最大,说明可以提升的通过率更大
double cp() const{
return (up + 1.0) / (dn + 1.0) - (up + 0.0) / dn;
}
bool operator < (const klass& k) const {
// return up / dn > k.up / k.dn;
return cp() < k.cp();
}
};
public:
double maxAverageRatio(vector<vector<int>>& classes, int extraStudents) {
priority_queue<klass> q;
for (int i = 0; i < classes.size(); i++) {
q.push( klass(classes[i][0], classes[i][1]) );
}
// 找到能使通过率提高更多的一个班级,选择该班级,加入一个可以通过的同学
while (extraStudents--) {
klass ktop = q.top();
q.pop();
q.push( klass(ktop.up + 1, ktop.dn + 1) );
}
double ans = 0;
while (!q.empty()) {
ans += q.top().up*1.0 / q.top().dn;
q.pop();
}
ans /= classes.size();
return ans;
}
};
3. 1499. 满足不等式的最大值
思路:
要求 $y_i + y_j + |x_i - x_j|$ 最大值,由于给定了 $x_i$ 严格递增,即$x_i < x_j$,那么可以将绝对值去掉,得到 $y_i + y_j + x_j - x_i$,调换一下顺序就是 $(y_i - x_i) + (y_j + x_j)$ ,那么就可以固定 $x_j, y_j$ 枚举满足 $x_j - x_i <= k$ 的 $i$ 的坐标,找到最大的 $y_i - x_i$ 即可。实现细节见代码注释。
class Solution {
public:
int findMaxValueOfEquation(vector<vector<int>>& points, int k) {
priority_queue<pair<int, int>> max_heap; // 不写 greater<>,或者less<>默认为大根堆
int n = points.size();
int ans = INT_MIN;
// 先放入第一个元素
max_heap.push(pair{points[0][1] - points[0][0], points[0][0]}); // pair((yi-xi), xi),堆对于pair类型默认按照 pair.first 排序
// 将第 j 个元素作为固定元素,枚举前面满足条件元素,(此时满足条件的j之前的元素应该在堆中)
for (int j = 1; j < n; j++) {
// 如果堆中点的横坐标不满足条件,则一直弹出,直到堆为空,或者找到满足 xj - xi < k 的元素为止
while (!max_heap.empty() && points[j][0] - max_heap.top().second > k) {
max_heap.pop();
}
// 找到的第一个满足条件的元素,即堆顶元素就是 yi - xi 最大的元素
if (!max_heap.empty()) {
ans = max(ans, max_heap.top().first + points[j][0] + points[j][1]);
}
// 将当前元素加入堆中,开始以下一个元素作为 xj 枚举前面满足条件的元素
max_heap.push(pair{points[j][1] - points[j][0], points[j][0]});
}
return ans;
}
};
4. 2163. 删除元素后和的最小差值
思路:
将长为 $3n$ 的区间分为左右两部分,分别求出每一部分区间内 $n$ 个元素的和最值,左边区间维护最小值,右边区间维护最大值,此时可以保证左右差值最小。如果视为将 $3n$ 的区间砍为两半,则落刀的位置在区间 $(n - 1, 2n)$ 之间。左右的最值分别利用最大堆和最小堆维护更方便。
class Solution {
public:
long long minimumDifference(vector<int>& nums) {
int n = nums.size() / 3;
priority_queue<int, vector<int>, less<int>> maxq; // 大顶堆
priority_queue<int, vector<int>, greater<int>> minq; // 小顶堆
long long ret = 1e10 + 10;
// left[0~n]存储了[0, n-1], [0, n], [0, n+1], [0, n+2], ... , [0, 2n-1] 共 n+1 个区间内的最小的 n 个数之和
long long left[100010];
memset(left, 0, sizeof(left)); /* 注意初始化数组(我本地正常,交上去错,卡了很久) */
for (int i = 0; i < n; i++) {
left[0] += nums[i];
maxq.push( nums[i] );
}
for (int i = n; i < 2*n; i++) {
left[i - n + 1] = left[i - n] + nums[i];
maxq.push( nums[i] );
int maxv = maxq.top();
maxq.pop();
left[i - n + 1] -= maxv;
}
// for (int i = 0; i <= n; i++) cout << left[i] << endl;
// sec计算[n, 3n-1], [n+1, 3n-1], [n+2, 3n-1], ... , [2n-1, 3n-1]共 n + 1 个区间内的最大的 n 个元素之和,但是右边区间的值不需要记录,每次算出来直接跟新答案即可
long long sec = 0;
for (int i = 2*n; i < 3*n; i++) {
sec += nums[i];
minq.push( nums[i] );
}
ret = left[n] - sec;
for (int i = 2*n - 1; i >= n; i--) {
sec += nums[i];
minq.push( nums[i] );
int minv = minq.top();
minq.pop();
sec -= minv;
ret = min(ret, left[i - n] - sec);
}
return ret;
}
};

东方欲晓,莫道君行早。

浙公网安备 33010602011771号