LeetCode 2386.找出数组的第 K 大和:逆向思维(小根堆)

【LetMeFly】2386.找出数组的第 K 大和:逆向思维(小根堆)

力扣题目链接:https://leetcode.cn/problems/find-the-k-sum-of-an-array/

给你一个整数数组 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 k k次。也就是说:

我们只需要找到这个递增非负数组的前 k k k个最小和即可。

假设这个处理后的数组是[1, 2, 3],那么怎么生成它的 2 3 2^3 23个子序列呢?

  • 初始序列为空:[]
  • 在空序列的基础上添加1得到[1]
  • [1]的基础上添加2得到[1, 2];或将最后的1替换成2得到[2]
  • [2]基础上添加3得到[2, 3];或直接替换最后的2[3]
  • [1, 2]的基础上添加3得到[1, 2, 3];或直接替换最后的2[1, 3]

这不是很适合最小堆吗?

堆中存放(当前和, 添加到了第几个元素),每次选当前和最小的出堆,即为下一个"最小序列和"。

  • 时间复杂度 O ( n log ⁡ n + k log ⁡ k ) O(n\log n + k\log k) O(nlogn+klogk),其中 n = l e n ( n u m s ) n=len(nums) n=len(nums)
  • 空间复杂度 O ( k ) O(k) O(k)

AC代码

C++
cpp 复制代码
/*
1 2 3

      1
    12  2
 123   23  3
*/

typedef long long ll;
typedef pair<ll, int> pli;
class Solution {
public:
    ll kSum(vector<int>& nums, int k) {
        ll sum = 0;
        for (int& t : nums) {
            if (t >= 0) {
                sum += t;
            }
            else {
                t = -t;
            }
        }
        sort(nums.begin(), nums.end());
        priority_queue<pli, vector<pli>, greater<pli>> pq;
        pq.push({nums[0], 0});
        ll toDesc = 0;
        for (int i = 1; i < k; i++) {
            auto [nowSum, th] = pq.top();
            toDesc = nowSum;
            pq.pop();
            if (th == nums.size() - 1) {
                continue;
            }
            pq.push({nowSum + nums[th + 1], th + 1});
            pq.push({nowSum - nums[th] + nums[th + 1], th + 1});
        }
        return sum - toDesc;
    }
};
Python
python 复制代码
# from typing import List
# import heapq

class Solution:
    def kSum(self, nums: List[int], k: int) -> int:
        sum_ = 0
        for i in range(len(nums)):
            if nums[i] >= 0:
                sum_ += nums[i]
            else:
                nums[i] = -nums[i]
        nums.sort()
        pq = [(nums[0], 0)]
        toDesc = 0
        for _ in range(1, k):
            nowSum, th = heapq.heappop(pq)
            toDesc = nowSum
            if th == len(nums) - 1:
                continue
            heapq.heappush(pq, (nowSum + nums[th + 1], th + 1))
            heapq.heappush(pq, (nowSum - nums[th] + nums[th + 1], th + 1))
        return sum_ - toDesc

同步发文于CSDN和我的个人博客,原创不易,转载经作者同意后请附上原文链接哦~

Tisfy:https://letmefly.blog.csdn.net/article/details/136588803

相关推荐
Swift社区18 分钟前
LeetCode - #139 单词拆分
算法·leetcode·职场和发展
Dong雨2 小时前
力扣hot100-->栈/单调栈
算法·leetcode·职场和发展
trueEve3 小时前
SQL,力扣题目1369,获取最近第二次的活动
算法·leetcode·职场和发展
九圣残炎5 小时前
【从零开始的LeetCode-算法】3354. 使数组元素等于零
java·算法·leetcode
程序猿小柒6 小时前
leetcode hot100【LeetCode 4.寻找两个正序数组的中位数】java实现
java·算法·leetcode
_OLi_6 小时前
力扣 LeetCode 106. 从中序与后序遍历序列构造二叉树(Day9:二叉树)
数据结构·算法·leetcode
我明天再来学Web渗透7 小时前
【SQL50】day 2
开发语言·数据结构·leetcode·面试
小叶lr8 小时前
idea 配置 leetcode插件 代码模版
java·leetcode·intellij-idea
理论最高的吻11 小时前
98. 验证二叉搜索树【 力扣(LeetCode) 】
数据结构·c++·算法·leetcode·职场和发展·二叉树·c
沈小农学编程11 小时前
【LeetCode面试150】——202快乐数
c++·python·算法·leetcode·面试·职场和发展