原题链接:2386. 找出数组的第 K 大和
题目描述:
给你一个整数数组 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 <= 10^5
-10^9 <= nums[i] <= 10^9
1 <= k <= min(2000, 2^n)
解题思路:
假设我们此时有一个数组[-1,-2,0,1,2,3],此时我们的最大子序列和为0+1+2+3=6,我们需要求得是第k大子序列和,我们需要将当前最大子序列和变小,要变小就是要减去某些正数或者加上某些负数,我们将所有的负数都取绝对值,那么此时的操作就变为了减去某些数。我们还需要知道的一点就是最大的子序列和减去最小的子序列和得到的就是最大的子序列和,最大的子序列和减去第二小的子序列和得到的就是第二大的子序列和,依此类推,最大的子序列和减去第k小的子序列和就是第k大的子序列和,所以就将原问题转换为了求第k小的子序列和,那么我们可以进行如下操作求出第k小的子序列和,需要先把序列中所有的负数变为正数:
当前序列为[2,1,3],从小到大排序之后变为[1,2,3]。
最开始是一个空序列[]
然后考虑元素1,可以插入原来序列的末尾,变为[1]
然后考虑元素2,可以考虑插入[1]的末尾变为[1,2],或者替换[1]的末尾元素1变为[2]
然后考虑元素3,可以插入[1,2]的末尾变为[1,2,3],或者替换[1,2]的末尾元素2变为[1,3]
或者将[2]的末尾元素2替换为3变为[3],或者插入[2]的末尾变为[2,3]
对于上述过程使用小根堆进行维护,可以从小到大生成第k小的子序列,然后我们的初始序列所有非负整数的和就是最大子序列和,然后最大子序列和减去第k小子序列和就是答案。
时间复杂度:O(nlog(n)+klog(k)),n表示nums的长度。
空间复杂度:O(k)。
cpp代码如下:
class Solution {
typedef long long LL;
typedef pair<LL,int>PLI;
public:
long long kSum(vector<int>& nums, int k) {
LL sum=0;
int n=nums.size();
for(int i=0;i<n;i++){
if(nums[i]>=0){
sum+=nums[i];
}else {
nums[i]=-nums[i];
}
}
sort(nums.begin(),nums.end());
priority_queue<PLI,vector<PLI>,greater<PLI>>pq;
pq.push({0,0});
while(--k){
auto [s,i]=pq.top();
pq.pop();
if(i<n){
pq.push({s+nums[i],i+1});
if(i){
pq.push({s+nums[i]-nums[i-1],i+1});
}
}
}
return sum-pq.top().first;
}
};