2163. 删除元素后和的最小差值
给你一个下标从 0 开始的整数数组 nums ,它包含 3 * n 个元素。
你可以从 nums 中删除 恰好 n 个元素,剩下的 2 * n 个元素将会被分成两个相同大小的部分。
前面 n 个元素属于第一部分,它们的和记为 sumfirst 。
后面 n 个元素属于第二部分,它们的和记为 sumsecond 。
两部分和的 差值 记为 sumfirst - sumsecond 。
-
比方说,sumfirst = 3 且 sumsecond = 2 ,它们的差值为 1 。
-
再比方,sumfirst = 2 且 sumsecond = 3 ,它们的差值为 -1 。
请你返回删除 n 个元素之后,剩下两部分和的 差值的最小值 是多少。
首先我们通过删除极端位置获得first段与second段的取值范围:
step1:
由此我们获得了
-
前段取值: [ 0 , 2 n − 1 ] [0, 2n-1] [0,2n−1]
-
后段取值: [ n , 3 n − 1 ] [n, 3n-1] [n,3n−1]
这意味着即使无规则的、随意的删除n个元素,前段后段的取值都不会超过上述范围。
step2:
获得了这个范围就可以进行枚举,计算最小差值。
在计算最小差值之前,我们需要明确最小差值的定义: d i f f = f i r s t − s e c o n d diff = first-second diff=first−second
根据最小差值的定义我们不难得出我们程序设计的目的:
- 前段数据 sumfirst 要尽可能小
- 后段数据 sumsecond 要尽可能大
为了实现这一目的,
对于前半段范围的数据进行大根堆存储(降序排列),
而对于后半段范围的数据进行小根堆存储(升序排列)
step3:
本题仍存在一个难点:前段取值与后端取值具有重合部分
对于这个难点我们将创建一个数组对 [ n , 2 n ] [n,2n] [n,2n]这部分数据进行维护,并且动态更新答案
具体我们将在代码中进行说明
class Solution {
public:
long long minimumDifference(vector<int>& nums) {
int n = nums.size()/3;
int i;
long long ans = INT_MAX;
priority_queue<int> maxq; // 前半部分尽可能删除的大,构建一个大根堆(less<T>)
priority_queue<int, vector<int>, greater<int>> minq;
// 后半部分尽可能删除的小,构建一个小根堆(great<T>)
long long first[200010]; // 在原数组中前i个元素选取最合理的n个元素的和
// 构建first数组的将为最后计算ans提供极大的帮助
**********************************************************************
// 计算前半部分
first[n-1] = 0; // 初始化前n-1个元素的和
// 如果出现了极端情况,删除的num为最后n个数据,则first的取值有唯一性,即为前n个数
// 换言之,后半部分无法取到这个范围内
// 没有调整的空间,这里的first[n-1]的值即模拟这种情况
for(i = 0; i < n; ++i) {
first[n-1] += nums[i];
maxq.push(nums[i]);
} // 前n个值照单全收(必须!)
for(i = n; i < 2*n; ++i) { // 现在开始计算前后段交界面
first[i] = first[i-1] + nums[i];
maxq.push(nums[i]);
int maxv = maxq.top();
maxq.pop();
first[i] -= maxv;
}
// 通过这里的代码我们明显看出来n~2n-1部分sum first的值可以进行调整
// 调整的大原则就是前半部分尽可能小,因此删除的数尽可能大
***************************************************************************
// 计算后半部分
long long sec = 0;
for(i = 3*n-1; i >= 2*n; --i) {
sec += nums[i];
minq.push(nums[i]);
} // 前段数据不可能达到这个长度,因此这部分数据照单全收
****************************************************************************
// 动态计算最大值:
ans = first[2*n-1]-sec;
// 这里相当于后段数据范围取到[2n~3n-1],前段数据取到[0,2n-1] --- 这只是起点
// 开始从起点一直推到终点 ---- 终点就是n 因为n后端数据无法取到 [0~n-1]
// 这里体现出了first数组的作用
for(i = 2*n-1;i>=n;--i) {
sec += nums[i];
minq.push(nums[i]);
int minv = minq.top();
minq.pop();
sec-=minv;
ans = min(first[i-1]- sec, ans); // 动态更新最小值
}
return ans;
}
};