本题为1月18日力扣每日一题
题目来源:力扣第2171题
题目tag:数位dp
动态规划
题面
题目描述
给定一个正整数数组beans,其中每个整数表示一个袋子里装的魔法豆的数目。
请你从每个袋子中拿出一些豆子(也可以不拿出),使得剩下的非空袋子中(即至少还有一颗魔法豆的袋子)魔法豆的数目相等。一旦把魔法豆从袋子中取出,你不能再将它放到任何袋子中。
请返回你需要拿出魔法豆的最少数目。
示例
示例 1
输入:
beans = [4,1,6,5]
输出:
4
解释:
- 我们从有1个魔法豆的袋子中拿出1颗魔法豆。
剩下袋子中魔法豆的数目为:[4,0,6,5] - 然后我们从有6个魔法豆的袋子中拿出2个魔法豆。
剩下袋子中魔法豆的数目为:[4,0,4,5] - 然后我们从有5个魔法豆的袋子中拿出1个魔法豆。
剩下袋子中魔法豆的数目为:[4,0,4,4]
总共拿出了 \(1 + 2 + 1 = 4\) 个魔法豆,剩下非空袋子中魔法豆的数目相等。
没有比取出4个魔法豆更少的方案。
示例 2
输入:
beans = [2,10,3,2]
输出:
7
解释:
- 我们从有 2 个魔法豆的其中一个袋子中拿出 2 个魔法豆。
剩下袋子中魔法豆的数目为:[0,10,3,2] - 然后我们从另一个有 2 个魔法豆的袋子中拿出 2 个魔法豆。
剩下袋子中魔法豆的数目为:[0,10,3,0] - 然后我们从有 3 个魔法豆的袋子中拿出 3 个魔法豆。
剩下袋子中魔法豆的数目为:[0,10,0,0]
总共拿出了 2 + 2 + 3 = 7 个魔法豆,剩下非空袋子中魔法豆的数目相等。
没有比取出 7 个魔法豆更少的方案。
提示
\(1 \leq beans.length \leq 10^5\)
\(1 \leq beans[i] \leq 10^5\)
思路分析
以前做过一道要求所有数相等求最少步数的题目,当时这题既可以增加也可以减少,是在转化问题后借助对顶堆
这种数据结构来实现的,因此这题我的第一反应也是用同样的方式转化问题然后尝试解决,发现并没有什么用。
于是我开始思考是否能用其他数据结构,思考了许久也没有什么答案。然后我想能不能找到一种使得结果单调的方式,然后使用二分
来解决问题。经过较长时间思考后我终于发现,这题只要直接暴力
即可。
首先显然,最终统一化成的数(除了0)一定是给定数组中的某一个元素,这个性质在此处不做证明。那么,我只需要枚举每一个元素,然后分别计算需要取出的糖果的数目即可。这个计算很简单,大于选定元素的数,取出他们的差值即可;小于选定元素的数,由于不能加糖果,所以全部取出。但是这样是一个平方级别的复杂度,显然会超时,有没有办法优化呢?
由于我是从二分
的角度入手思考的,所以我直接找到了解决的办法。对于正常的思考逻辑,我想可能可以这样理解:
- 之所以直接枚举会使得复杂度达到平方,是因为在每次枚举一个数之后,我们一直在重复遍历整个数组来计算差值
- 能否采取一种方式可以利用前一次计算的结果来得出当前的结果,省略掉重复的过程?
- 因为这个结果实际上是差值,显然我每次调高或减小基准值时,每个大于等于新基准值的数所贡献的差值变化就是基准值的变化,因为 $(a - b) - (a - c) = c - b $ ;小于原本基准值的数的贡献不会改变,因为他们已经被减到0了;在两者之间的数(前闭后开)的贡献的变化就是原本的基准值,因为 \(a - (a - b) = b\)
- 所以问题变成给定数组中的一个值,我能否快速知道数组中几个数比他小,几个数比他大。非常容易,我给数组排个序就好了。而且在排序后按顺序进行遍历时,你会发现上述所说的“在两者之间的数“其实只有前一次取的那个数,所以只需要直接将其从贡献中减掉即可
有了上面这个优化思路后,本题的做法非常容易。首先对数组进行一次排序,然后从小到大遍历整个数组,依次把每个数当成当前的基准值。每次的取出糖果数,就是上一次的取出糖果数,减去上一个基准值(介于两个基准值之间的数的贡献变化),然后加上当前元素及以后的元素个数乘上两次基准值的差(大于等于当前基准值的数的贡献变化)即可。这里注意计算第一个元素为基准值时的情况中,由于没有上一次取出糖果数,无法递推,因此只能直接遍历整个数组,依次计算差值。
注意此题的中间结果可能很大,会超过int
的上限,需要开long long
。十年oi一场空,不开long long见祖宗。
参考代码
class Solution {
public:
long long minimumRemoval(vector<int>& beans) {
sort(beans.begin(),beans.end());
// 第一个元素没有前一次的结果,直接计算
long long cnt = 0;
for(auto w : beans) {
cnt += w - beans[0];
}
long long res = cnt;
// 依次枚举每一项
for(int i = 1;i < beans.size();i++){
// 前一项变成0,贡献变化为前一项
cnt += beans[i - 1];
// 后面每项贡献变化为两次基准值的差值
cnt -= 1LL * (beans[i] - beans[i - 1]) * (beans.size() - i);
res = min(res,cnt);
}
return res;
}
};
标签:拿出,基准值,魔法,long,力扣,2171,beans,袋子 From: https://www.cnblogs.com/geministar/p/-/LeetCode2171"正是我们每天反复做的事情,最终造就了我们,优秀不是一种行为,而是一种习惯" ---亚里士多德