Hard

题目描述

给你一个整数数组 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)

解题思路

这是一道经典的第K大问题,核心思路是从最大和开始,逐步生成次大和

分析思路:

  1. 最大的子序列和是所有非负数的和
  2. 从任意一个子序列和,我们可以通过两种操作得到更小的和:
    • 如果当前包含某个正数,移除它
    • 如果当前不包含某个负数,添加它

算法步骤:

  1. 将数组分为正数和负数两部分,正数按降序排列,负数按绝对值升序排列
  2. 计算所有正数的和作为初始最大和
  3. 使用优先队列(最大堆)维护候选和,每次取出最大的和
  4. 对于每个取出的和,生成两个新的候选和:
    • 移除一个正数(如果有的话)
    • 添加一个负数(如果有的话)
  5. 重复k-1次得到第k大和

优化要点:

  • 使用状态压缩,用索引表示当前操作到哪个位置
  • 避免重复状态的生成

代码实现

class Solution {
public:
    long long kSum(vector<int>& nums, int k) {
        long long maxSum = 0;
        vector<int> abs_nums;
        
        for (int num : nums) {
            if (num >= 0) {
                maxSum += num;
            }
            abs_nums.push_back(abs(num));
        }
        
        sort(abs_nums.begin(), abs_nums.end());
        
        priority_queue<pair<long long, int>> pq;
        pq.push({maxSum, 0});
        
        for (int i = 1; i < k; i++) {
            auto [sum, idx] = pq.top();
            pq.pop();
            
            if (idx < abs_nums.size()) {
                pq.push({sum - abs_nums[idx], idx + 1});
                if (idx > 0) {
                    pq.push({sum - abs_nums[idx] + abs_nums[idx - 1], idx + 1});
                }
            }
        }
        
        return pq.top().first;
    }
};
class Solution:
    def kSum(self, nums: List[int], k: int) -> int:
        max_sum = sum(x for x in nums if x >= 0)
        abs_nums = sorted([abs(x) for x in nums])
        
        heap = [(-max_sum, 0)]
        
        for _ in range(k - 1):
            neg_sum, idx = heappop(heap)
            sum_val = -neg_sum
            
            if idx < len(abs_nums):
                heappush(heap, (-(sum_val - abs_nums[idx]), idx + 1))
                if idx > 0:
                    heappush(heap, (-(sum_val - abs_nums[idx] + abs_nums[idx - 1]), idx + 1))
        
        return -heap[0][0]
public class Solution {
    public long KSum(int[] nums, int k) {
        long maxSum = 0;
        var absNums = new List<int>();
        
        foreach (int num in nums) {
            if (num >= 0) {
                maxSum += num;
            }
            absNums.Add(Math.Abs(num));
        }
        
        absNums.Sort();
        
        var pq = new PriorityQueue<(long sum, int idx), long>(
            Comparer<long>.Create((a, b) => b.CompareTo(a))
        );
        pq.Enqueue((maxSum, 0), maxSum);
        
        for (int i = 1; i < k; i++) {
            var (sum, idx) = pq.Dequeue();
            
            if (idx < absNums.Count) {
                pq.Enqueue((sum - absNums[idx], idx + 1), sum - absNums[idx]);
                if (idx > 0) {
                    long newSum = sum - absNums[idx] + absNums[idx - 1];
                    pq.Enqueue((newSum, idx + 1), newSum);
                }
            }
        }
        
        return pq.Peek().sum;
    }
}
var kSum = function(nums, k) {
    let maxSum = 0;
    const absNums = [];
    
    for (const num of nums) {
        if (num >= 0) {
            maxSum += num;
        }
        absNums.push(Math.abs(num));
    }
    
    absNums.sort((a, b) => a - b);
    
    const pq = new MaxPriorityQueue({ priority: x => x[0] });
    pq.enqueue([maxSum, 0]);
    
    for (let i = 1; i < k; i++) {
        const [sum, idx] = pq.dequeue().element;
        
        if (idx < absNums.length) {
            pq.enqueue([sum - absNums[idx], idx + 1]);
            if (idx > 0) {
                pq.enqueue([sum - absNums[idx] + absNums[idx - 1], idx + 1]);
            }
        }
    }
    
    return pq.front().element[0];
};

复杂度分析

复杂度大小
时间复杂度O(n log n + k log k)
空间复杂度O(n + k)

其中 n 是数组长度。时间复杂度主要由排序 O(n log n) 和堆操作 O(k log k) 组成。空间复杂度主要用于存储绝对值数组和优先队列。