• 二分/树上第k短路,LeetCode2386. 找出数组的第 K 大和


    一、题目

    1、题目描述

    给你一个整数数组 nums 和一个  整数 k 。你可以选择数组的任一 子序列 并且对其全部元素求和。

    数组的 第 k 大和 定义为:可以获得的第 k 个 最大 子序列和(子序列和允许出现重复)

    返回数组的 第 k 大和 。

    子序列是一个可以由其他数组删除某些或不删除元素排生而来的数组,且派生过程不改变剩余元素的顺序。

    注意:空子序列的和视作 0 

    2、接口描述

    1. class Solution {
    2. public:
    3. long long kSum(vector<int>& nums, int k) {
    4. }
    5. };

    3、原题链接

    2386. 找出数组的第 K 大和 - 力扣(LeetCode)


    二、解题报告

    1、思路分析

    首先明确最大子序列和就是所有正数之和sum

    那么第2大就是sum加上一个非负数或者从正数中拿掉一个

    那么我们可以将原数组全部取绝对值,然后求取绝对值后的第k - 1小序列和s

    那么答案就是sum - s

    F1 二分+枚举子集

    给定mid,如何判断是否至少有k个子序列和不大于mid

    对于每个nums[i]都有选或不选两种情况,那么我们递归枚举子序列

    正常而言,枚举所有子序列是2^n,但是我们没必要全部枚举

    我们可以将nums按升序排序

    如果枚举到i,上一个子序列和为s,那么如果s + nums[i] > mid,我们就剪枝

    否则cnt++,然后继续向下枚举

    我们发现递归次数和cnt+1的次数有关,而cnt最多加k次,所以我们可以O(k)解决

    然后定义二分边界l = 0, r = sum(nums)(nums取绝对值且排序后)

    不断二分即可

    F2 转化为树上第k短路

    我们考虑取绝对值且排序后nums枚举子集在树上表示如下:

    每个节点代表一个子序列,显然越往下节点的权值越大

    那么我们只要找到从根节点出发的第k - 1短路即可

    其实就是树上dijkstra,用小根堆存储节点权值和下一个元素的下标即可

    2、复杂度

    F1:时间复杂度:O(nlogn+klogU),即排序和二分 空间复杂度:O(min(k,n)),即取决于递归深度

    F2:时间复杂度:O(nlogn+klogk)空间复杂度:O(k)

    3、代码详解

    ​F1
    1. class Solution {
    2. public:
    3. long long kSum(vector<int>& nums, int k) {
    4. long long sum = 0;
    5. for(int& x : nums) if(x >= 0) sum += x; else x = -x;
    6. sort(nums.begin(), nums.end());
    7. function<bool(long long)> check = [&](long long lim){
    8. int cnt = 1;
    9. function<void(int, long long)> dfs = [&](int i, long long s){
    10. if(cnt == k || i == nums.size() || s + nums[i] > lim)
    11. return;
    12. ++cnt, dfs(i + 1, s + nums[i]), dfs(i + 1, s);
    13. };
    14. dfs(0, 0);
    15. return cnt == k;
    16. };
    17. long long l = 0, r = accumulate(nums.begin(), nums.end(), 0LL);
    18. while(l < r){
    19. long long mid = l + r >> 1;
    20. if(check(mid)) r = mid;
    21. else l = mid + 1;
    22. }
    23. return sum - r;
    24. }
    25. };

    F2

    1. class Solution {
    2. public:
    3. long long kSum(vector<int>& nums, int k) {
    4. long long sum = 0;
    5. for(int& x : nums) if(x >= 0) sum += x; else x = -x;
    6. sort(nums.begin(), nums.end());
    7. priority_queuelong long, int>, vectorlong long, int>>, greaterlong long, int>>> pq;
    8. pq.emplace(0, 0);
    9. while(--k){
    10. auto [s, i] = pq.top();pq.pop();
    11. if(i < nums.size()) {
    12. pq.emplace(s + nums[i], i + 1);
    13. if(i) pq.emplace(s + nums[i] - nums[i - 1], i + 1);
    14. }
    15. }
    16. return sum - pq.top().first;
    17. }
    18. };

  • 相关阅读:
    java版的上门家政系统和PHP版的上门家政有什么区别?
    我们再来谈一谈HashMap呗(负载因子,遍历角度)
    使用React和Node构建实时协作的白板应用
    数据库3、4、5章作业
    【编程题】【Scratch三级】2020.06 海底寻宝
    14.Oracle中的事务
    Decomposed Meta-Learning for Few-Shot Named Entity Recognition
    xxl-job架构原理
    2023年【天津市安全员C证】报名考试及天津市安全员C证操作证考试
    【荣耀内推】2023届荣耀校招开启啦
  • 原文地址:https://blog.csdn.net/EQUINOX1/article/details/136579600