题目链接:2386. 找出数组的第 K 大和 - 力扣(LeetCode)
给你一个整数数组 nums
和一个 正 整数 k
。你可以选择数组的任一 子序列 并且对其全部元素求和。
数组的 第 k 大和 定义为:可以获得的第 k
个 最大 子序列和(子序列和允许出现重复)
返回数组的 第 k 大和 。
子序列是一个可以由其他数组删除某些或不删除元素排生而来的数组,且派生过程不改变剩余元素的顺序。
**注意:**空子序列的和视作 0
。
示例 1:
输入:nums = [2,4,-2], k = 5
输出:2
解释:所有可能获得的子序列和列出如下,按递减顺序排列:
- 6、4、4、2、2、0、0、-2
数组的第 5 大和是 2 。
示例 2:
输入:nums = [1,-2,3,4,-10,12], k = 16
输出:10
解释:数组的第 16 大和是 10 。
提示:
n == nums.length
1 <= n <= 105
-109 <= nums[i] <= 109
1 <= k <= min(2000, 2n)
困难题 cv大法
class Solution {
public:
long long kSum(vector &nums, int k) {
long sum = 0;
for (int &x : nums) {
if (x >= 0) {
sum += x;
} else {
x = -x;
}
}
ranges::sort(nums);
auto check = [&](long sum_limit) -> bool {
int cnt = 1;
function dfs = [&](int i, long long s) {
if (cnt == k || i == nums.size() || s + nums[i] > sum_limit) {
return;
}
cnt++; // s + nums[i] <= sum_limit
dfs(i + 1, s + nums[i]);
dfs(i + 1, s);
};
dfs(0, 0);
return cnt == k;
};
long long left = -1, right = accumulate(nums.begin(), nums.end(), 0LL);
while (left + 1 < right) {
long long mid = (left + right) / 2;
(check(mid) ? right : left) = mid;
}
return sum - right;
}
};
class Solution {
public long kSum(int[] nums, int k) {
long sum = 0, right = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] >= 0) {
sum += nums[i];
} else {
nums[i] = -nums[i];
}
right += nums[i];
}
Arrays.sort(nums);
long left = -1;
while (left + 1 < right) { // 开区间二分,原理见【前置知识】
long mid = (left + right) / 2;
cnt = k - 1; // 空子序列算一个
dfs(0, mid, nums);
if (cnt == 0) { // 找到 k 个元素和不超过 mid 的子序列
right = mid;
} else {
left = mid;
}
}
return sum - right;
}
private int cnt;
// 反向递归,增加改成减少,这样可以少传一些参数
private void dfs(int i, long s, int[] nums) {
if (cnt == 0 || i == nums.length || s < nums[i]) {
return;
}
cnt--;
dfs(i + 1, s - nums[i], nums); // 选
dfs(i + 1, s, nums); // 不选
}
}