找出数组的第k大和
给你一个整数数组 nums 和一个正整数 k 。你可以选择数组的任一子序列并且对其全部元素求和。
数组的第 k 大和 定义为:可以获得的第 k 个 最大 子序列和(子序列和允许出现重复)
返回数组的第 k 大和
1. 转化问题 + 大根堆
class Solution {
public:
long long kSum(vector<int> &nums, int k) {
long sum = 0L;
for (int &x : nums) //对正数求和,同时将负数变为正数
if (x >= 0) sum += x;
else x = -x;
sort(nums.begin(), nums.end()); //排序
//原问题转化成为从原数组中选择元素减去
priority_queue<pair<long, int>> pq; //大根堆,存储扩张时的状态,类似深度优先搜索
pq.emplace(sum, 0); //第一大
while (--k) { //循环运行k-1次
auto[sum, i] = pq.top(); //当前最大值和当前待操作下标
pq.pop();
if (i < nums.size()) {
//扩张时两种选择,第一种减去当前操作数,第二种跳过当前操作数
//但是由于涉及到大根堆优先级,所以不能直接跳过,而是后面补回来
pq.emplace(sum - nums[i], i + 1); // 保留 nums[i-1]
if (i) pq.emplace(sum - nums[i] + nums[i - 1], i + 1); // 不保留 nums[i-1],把之前减去的加回来
}
}
return pq.top().first;
}
};
2. 二分
class Solution {
public:
long long kSum(vector<int> &nums, int k) {
//预处理
long sum = 0L;
for (int &x : nums) {
if (x >= 0) sum += x;
else x = -x;
}
sort(nums.begin(), nums.end());
--k;//
auto check = [&](long limit) -> bool {
int cnt = 0;
//暴搜所有小于等于mid的序列和个数
function<void(int, long)> f = [&](int i, long s) {
if (i == nums.size() || cnt >= k || s + nums[i] > limit) return; //边界条件
++cnt; //因为后面都可以不选,满足条件序列个数加一
f(i + 1, s + nums[i]); // 选
f(i + 1, s); // 不选
};
f(0, 0L);
return cnt >= k;
};
//求最大的结果,从数组中减去最小的序列,即有k个序列和满足值即可
long left = 0L, right = accumulate(nums.begin(), nums.end(), 0L);
while (left < right) {
long mid = (left + right) / 2; //累计和
if (check(mid)) right = mid; //小于等于累计和的序列个数大于等于阈值
else left = mid + 1; //小于阈值,不满足条件
}
return sum - left; //减去对应累计和
}
};