理解在代码注释中了捏。
给你一个整数数组 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 <= 105
-109 <= nums[i] <= 109
1 <= k <= min(2000, 2n)
//
// Created by py on 24-3-12.
//
#include <iostream>
#include <vector>
#include <queue>
#include <unordered_map>
#include <deque>
#include <cstdio>
#include <cstdlib>
#include <algorithm>
using namespace std;
/*
* *************************************
* 这里大半感兴趣看看不感兴趣可以直接跳下边的****分割线
*
* 本题使用优先队列来进行优化找到本次进循环的最大值
* 本题意思找到子序列中的第k大的值,我们在观察取值范围的时候发现这个序列中可能存在负数
* 下面是思路起始:
* 找到最大值我们只需要从大到小排序之后一直加到负数的起始就可以找到最大值(非常简单)
* 但我们应该如何找到第二大的值呢
* 我是直接想到,刚刚开始找最大值的时候我们是不是已经找到开始负数的地方了?
* 比如我们排序之后的数组是:6 , 4 , 3 , -1 , -2 (最大值子序列为:6 , 4 , 3 )
* 第二大我是不是可以比较(假设有 0 即正负数的分界值 ) 0 这个分界值左右边的绝对值大小呢?
* 假如说 正数 的 比负数的大,那么我在最大值的子序列基础上再加一个 -1(本假设的数组)
* 就直接可以得到第二大的值(6 , 4 , 3 , -1)了?
* 或者反过来说 正数的绝对值小于 负数的绝对值:此时假设排序数组是:6 , 4 , 1 , -3 , -4(最大值子序列为:6 , 4 , 1)
* 那么我在最大值的子序列基础上,不要这个正数了,是不是就是得到第二大的呢( 6 , 4 )?
*
*
* 只是这种方法搞了半天,找第三个的时候,人脑就不怎么ok了,我是从第二大的子序列开始找还是第一大的子序列开始找呢?
* 也就是数组:6 , 4 , 3 , -1 , -2 (最大值子序列为:6 , 4 , 3 )
* 第二大的是(6 , 4 , 3 , -1)第三大就有几个得判断的了:1. 这次我在最大子序列不要 3 (6,4)
* 2. 这次我在最大子序列要个 -2 (6,4,3,-2)
* 3. 这次我在第二大要个 -2 (6,4,3,-1,-2)
* 4. 这次我在第二大不要个 3 (6,4,-1)
* 说实话我当时是想着暴力递归的,之后滚去看了一眼标签:优先队列
* 把所有找到的第几大值入队,之后等队列满怎么多就跳出循环?还是找下一个第几大子序列和的时候从那个已知的子序列找呢?
* 第一个我敲敲半天就发觉我还是逃脱不了第二个问题(还是找下一个第几大子序列和的时候从那个已知的子序列找呢?)
*
* *********************************************************************
* 最后在这个人脑思考的过程中,我发现我们都是在做减法从最大值的时候开始就是判断不要正数(减掉)?还是要多一个负数(减掉其绝对值)
* 这样我们就可以不要去思考什么加减,在判断的时候就一直减绝对值就行了
* 我们可以直接排序就可以不用 0 这个分割线左右横跳找正负数绝对值谁大谁小了
* ***********************************************************************
*
* 假如我们原本nums数组是:( 4 , 2 , - 2)(最大子序列的和是 6 )
* 根据上边分割线的方法,再进行从小到大排序后是 (2 , 2 ,4)
* 这里我们可以使用优先队列保存我们找到可能是最几大的值,第一次出队的代表是第一大的,那么第二次出队就是代表第二大的
* 那么第 k 次出队的就是我们想要的答案(这里不考虑越界问题)
* 第二大的必定是最大子序列的和 减去 排序后的第一小的值也就是nums[0]
* 第三大的也必定是最大子序列的和 减去 排序后的第一小的值也就是nums[1]
* 第四大就得思考一会了:是继续最大和减nums[2]?还是利用最大和减(nums[0]和nums[1])?
* 所以说请看代码(有注释解释每一行有啥用)
* */
class Solution {
public:
long long kSum(vector<int> nums, int k) {
/*
* 优先队列是内置堆排序,小根堆就是less,大根堆就是greater
* top()返回的就是队列中最大或者最小值
* */
// 返回的都是long long了优先队列不存long long是不是不太可能?
// 这里的long long 是存和; int 是这次要操作那个数组nums下标的值了?
priority_queue<pair<long long, int>, vector<pair<long long, int> >, less<pair<long long, int>>> priorityQueue;
long long total = 0;//找到最大值存起来
for (int i = 0; i < nums.size(); ++i) {
if (nums[i] >= 0) total += nums[i];//计算最大值正数加起来
else nums[i] *= -1; // 负数直接去反
}
//将新数组排序(从小到大)
sort(nums.begin(), nums.end());
//将最大的入队再出队就少一次 k
priorityQueue.push({total, 0});
while (k-- > 1) {
// 将最大的取出来,(贪心一下)
auto [value, index] = priorityQueue.top();
priorityQueue.pop();// 出队,即又出一个最大的,离第k大越来越近
if (index == 0) {
// 操作index 0
priorityQueue.push({value - nums[index], index + 1});
continue;
}
// 再来溢出了
if (index == nums.size()) continue;
/*
* 这里有着一种dp的感觉,但是实际上是使用二叉树树,遍历该节点所有可能(使用贪心思想来使得每一次进队都是递减式进队)
* 优先队列会帮我们使用堆排序进行优化下一次出队的是那个幸运儿的
* ***********************************************************
* 要上一次减去的值(有可能那个值是正数或者是负数,但是在上诉所说,都是减去罢了)
* 不要这次的值(子序列不要这次的正数或者是负数)这个操作都是减去这个值的绝对值
* */
priorityQueue.push({value - nums[index] + nums[index - 1], index + 1});
/*
* 不要这次的值
* */
priorityQueue.push({value - nums[index], index + 1});
}
// 返回第 k 大的
return priorityQueue.top().first;
}
};
int main() {
Solution solution;
cout << solution.kSum({-4, 2, 3}, 6);
return 0;
}