2386. 找出数组的第 K 大和

30 阅读3分钟

2386. 找出数组的第 K 大和

image.png

image.png

image.png

优先队列 O(nlogn+klogk)O(k+logn)\lgroup O(n \log n + k \log k)、O(k + \log n)\rgroup

class Solution:
    def kSum(self, nums: List[int], k: int) -> int:        
        # 预处理:  求所有正数的和   将负数取负【获得 所有数的绝对值】——> 后续选这个数 相当于 - nums[i]

        n = len(nums)
        total = 0
        for i in range(n):
            if nums[i] > 0:
                total += nums[i]
            elif nums[i] < 0:
                nums[i] = -nums[i]
            # 0 的情况 无需 应对
        nums.sort() 

        # [1 2 3 4]
        # 
        ret = 0  # return  以下程序要返回的结果 前 k 个 最小的和  
        pq = [(nums[0], 0)] #  最后的元素为nums[i]的子序列和  索引 i 【记录索引为了进行取值和边界判断】       第 1 小。 
        for _ in range(2, k + 1): # 获得 第 _ 小 的值 这里第1次循环 获得 第 2 小的
            cur, i = heappop(pq)  # 弹出 最小的  [1, 2]
            ret = cur # 
            if i < n - 1:  # 因为 编号到 i + 1 已经处理到最后一个了
                heappush(pq, (cur + nums[i + 1], i + 1))    # 选 前一个  [1, 2, 3] 和为 6
                heappush(pq, (cur - nums[i] + nums[i + 1], i + 1))   # 不选前一个  [1, 3] 和为 4 更小

        return total - ret   

image.png

class Solution {
public:
    long long kSum(vector<int>& nums, int k) {
        // 优先队列  O(n log n + k log k)、 O(k + log n)

        int n = nums.size();
        long long total = 0; // 正数的和
        for (int i = 0; i < n; ++i){
            if (nums[i] > 0){
                total += nums[i];
            }else if (nums[i] < 0){
                nums[i] = -nums[i];
            }
        }
        sort(nums.begin(), nums.end());

        long long ret = 0;// 返回 第 k 个 最小的序列和  针对 处理过后的 nums 
        priority_queue< pair<long long, int>, vector< pair<long long, int> >, greater<>> pq;
        pq.push({nums[0], 0});
        for (int j = 2; j <= k; ++j){
            auto [cur, i] = pq.top(); pq.pop();
            ret = cur;
            if (i < n - 1){
                pq.push({cur + nums[i + 1], i + 1});
                pq.push({cur - nums[i] + nums[i + 1], i + 1});
            }
        }
        return total - ret;        
    }
};

二分查找 O(nlogn+klogS)O(min(n,k))\lgroup O(n \log n + k \log S)、O(min(n, k))\rgroup

class Solution:
    def kSum(self, nums: List[int], k: int) -> int:
        # 二分  是否 至多有 k 个 子序列, 其元素和  不超过 sumLimit 
        # 二分要求的单调性: 该题的 sumLimit越大, 子序列 越多

        # 二分查找 满足条件的子序列和   0 - sum(|nums[i]|)
        
        # 预处理:  求所有正数的和   将负数改成正数, 求 绝对值 和
        n = len(nums)
        total = 0 
        total2 = 0   # 计算  sum(|nums[i]|) 
        for i in range(n):
            if nums[i] > 0:
                total += nums[i]
            elif nums[i] < 0:
                nums[i] = -nums[i]            
            total2 += nums[i]
        nums.sort() 

        # 
        cnt = 0  # 子序列和 小于等于 mid 的非空子序列个数
        def dfs(i, cur, limit): # 当前子序列的和 为 cur , 讨论要不要 加 nums[i] 
            nonlocal cnt 
            if i == n or cnt == k - 1 or cur + nums[i] > limit:
                return 

            cnt += 1
            dfs(i + 1, cur + nums[i], limit) # 选 nums[i]
            dfs(i + 1, cur, limit)  #  不选 nums[i]


        left = 0
        right = total2   # sum(|nums[i]|) 
        while left <= right:
            mid = (left + right) // 2
            cnt = 0
            dfs(0, 0, mid)
            if cnt >= k - 1:
                right = mid - 1
            else:
                left = mid + 1
        return total - left

image.png

class Solution {
public:
    long long kSum(vector<int>& nums, int k) {
        // 二分法  O(n log n + k log S)  O(min(n, k))
        int n = nums.size();
        long long total = 0, total2 = 0; // 所有正数的和, 绝对值的和
        for (int i = 0; i < n; ++i){
            if (nums[i] > 0){
                total += nums[i];
            }else if (nums[i] < 0){
                nums[i] = -nums[i];
            }
            total2 += nums[i];
        }
        sort(nums.begin(), nums.end());

        function<void(int, long long, long long, int &)> dfs = [&](int i, long long t, long long limit, int &cnt){
            if (i == n || cnt == k - 1 || t + nums[i] > limit){
                return;
            }
            cnt++;
            dfs(i + 1, t + nums[i], limit, cnt);
            dfs(i + 1, t, limit, cnt);
        };

        long long left = 0, right = total2;
        while (left <= right){
            long long mid = (left + right) / 2;
            int cnt = 0;
            dfs(0, 0, mid, cnt);
            if (cnt >= k - 1){// 注意 这里是 k - 1
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
        return total - left;
    }
};