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

相关推荐
硕风和炜5 小时前
【LeetCode:3033. 修改矩阵 + 模拟】
java·算法·leetcode·矩阵·模拟
当年拼却醉颜红6 小时前
力扣爆刷第161天之TOP100五连刷71-75(搜索二叉树、二维矩阵、路径总和)
算法·leetcode·矩阵
中草药z7 小时前
【Java算法】二分查找 上
数据结构·笔记·算法·leetcode·二分查找·学习方法
m0_571957587 小时前
Java | Leetcode Java题解之第219题存在重复元素II
java·leetcode·题解
Mopes__7 小时前
Python | Leetcode Python题解之第220题存在重复元素III
python·leetcode·题解
DdddJMs__1358 小时前
C语言 | Leetcode C语言题解之第220题存在重复元素III
c语言·leetcode·题解
m0_571957588 小时前
Java | Leetcode Java题解之第218题天际线问题
java·leetcode·题解
DdddJMs__1359 小时前
C语言 | Leetcode C语言题解之第211题添加与搜索单词-数据结构设计
c语言·leetcode·题解
ddddd码10 小时前
力扣-贪心算法4
算法·leetcode·贪心算法
ZShiJ11 小时前
【题解】—— LeetCode一周小结27
算法·leetcode·职场和发展