Every day a Leetcode
题目来源:2171. 拿出最少数目的魔法豆
解法1:数学
我们可以将问题转化为:
寻找某一个数字 x,当我们将豆子数量小于 x 的袋子清空,并将豆子数量大于 x 的袋中豆子数量变为 x 时,拿出的豆子数量最少。
那么,x 一定等于某一个袋子的豆子数。
证明:力扣官方题解
我们对数组 beans 从小到大排序,最少的豆子数量即为:
那么,我们只需要遍历排序后的 beans 数组,并维护上式的最小值,最终将最小值返回作为答案即可。
代码:
/*
* @lc app=leetcode.cn id=2171 lang=cpp
*
* [2171] 拿出最少数目的魔法豆
*/
// @lc code=start
// 数学
class Solution
{
public:
long long minimumRemoval(vector<int> &beans)
{
int n = beans.size();
long long total = accumulate(beans.begin(), beans.end(), 0LL);
sort(beans.begin(), beans.end());
long long max_area = 0;
for (int i = 0; i < n; i++)
{
// 相当于在一个单调曲线下取一个最大矩形
max_area = max(max_area, (long long)(n - i) * beans[i]);
}
return total - max_area;
}
};
// @lc code=end
结果:
复杂度分析:
时间复杂度:O(nlogn),其中 n 是数组 beans 的长度。
空间复杂度:O(1)。
解法2:排序 + 前缀和
如果 beans[i] 保留了,那么小于 beans[i] 的数字都需要变为 0,大于 beans[i] 的数字都要变为 beans[i]。
算法:
- 排序后,枚举 beans[i] 作为最后保留的数字,取 min(cost)。
- 左边要删除的魔法豆个数为 preSum[i],右边要删除的魔法豆个数为 (preSum[n] - preSum[i]) - (n - i) * beans[i],所以 cost[i] = preSum[i] + (preSum[n] - preSum[i]) - (n - i) * beans[i]。
代码:
// 排序 + 前缀和
class Solution
{
public:
long long minimumRemoval(vector<int> &beans)
{
int n = beans.size();
sort(beans.begin(), beans.end());
vector<long long> preSum(n + 1, 0);
for (int i = 1; i <= n; i++)
preSum[i] = preSum[i - 1] + beans[i - 1];
long long minDelete = LONG_LONG_MAX;
for (int i = 0; i < n; i++)
{
// 左边要删除的魔法豆个数
long long leftSum = preSum[i];
// 右边要删除的魔法豆个数
long long rightDelete = (preSum[n] - preSum[i]) - (long long)(n - i) * beans[i];
minDelete = min(minDelete, leftSum + rightDelete);
}
return minDelete;
}
};
结果:
复杂度分析:
时间复杂度:O(nlogn),其中 n 是数组 beans 的长度。
空间复杂度:O(n),其中 n 是数组 beans 的长度。