Leetcode 2171 拿出最少数目的魔法豆
2171.拿出最少数目的魔法豆
给定一个 正整数 数组 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 <= beans.length <= 10^5
1 <= beans[i] <= 10^5
Related Topics
- 数组
- 前缀和
- 排序
题目链接: link
解答
今天这题虽然是 Medium, 但是达到一个中规中矩的效果还是不困难的,最简单粗暴的方式就是对 beans
的每一个元素进行遍历, 都假设当前为最小值或者0,计算需要的次数,这样的时间负载度为 O(n^2)
, 不知道会不会超时, 但是有一个更自然的想法,并且时间复杂度更低。
因为题目的意思就是,我要让所有看的到的元素一样高。以 [2, 10, 3, 4]
为例
那我们就需要移除选中部分
当我们将最小的部分先拿为0,那么我们有机会将需要拿的次数减少,如图所示
虽然因为先将最小的部分拿为0,增加了要拿的次数,但是在其他上减小了需要拿的次数,有机会减小的次数大于增加的次数。那么能否先将其他的部分减少到最小呢?实际上不能,我们每次要拿为0,必须是最小的,证明很简单,如图所示,以第二小为例
我们发现不仅拿的次数没有减少,而且还在原来的基础上增加了一个最小值的量。那等于说如果我当前要将一个值拿到为0,就需要知道当前最小值的是多少,那如果我每次都需要知道最小值是多少都需要遍历一遍剩余值,这个比较麻烦,并且这题的结果与数如何排列是无关的,因此我们很容易想到先对 beans
进行从小到大排序。
我们从0开始,计算如果0个值拿到为0达到题目要求的次数需要多少次作为基础结果。然后依次进行增加需要拿到为0的列,我们以下图为例(升序),当第一列拿到为0,那么增加的次数为第一列的值,减少的次数为(剩下的列的数量)* (第二列与第一列所差的值)
因此当将索引为 i
的列置为0时,前面的 [0,i-1]
列都已经为0了,此时要将临时结果加上第 i 列的值 , 并且减去 (索引为 i+1
列的值 减去 索引为 i
的列的值)乘以 剩余的列数, 即 (n-i-1), 并且只剩下最后一列时就不需要减了,因为只剩最后一列不为0时无论值为多少都是满足题目要求的了。在这个过程中一直看,从0个列置为0到n-1个列值为0,什么时候的结果是最小的,最后返回这个结果。
代码如下:
class Solution {
public long minimumRemoval(int[] beans) {
long result = 0;
ArrayList<Integer> list = new ArrayList();
for (int i = 0; i < beans.length; i++) {
list.add(beans[i]);
}
list.sort(Comparator.naturalOrder());
int[] diff = new int[beans.length];
for (int i = 0; i < diff.length; i++) {
diff[i] = list.get(i)-list.get(0);
}
for (int i = 0; i < diff.length; i++) {
result += diff[i];
}
long tempResult = result;
for (int i = 0; i < beans.length-1; i++) {
tempResult += list.get(i);
tempResult -= (list.get(i+1)-list.get(i))*(beans.length - i - 1);
if(tempResult<result){result = tempResult;}
}
return result;
}
}
但是这个版本实在是太慢了, 用时154ms, 击败了 6.02%, 于是看了下别人的逻辑, 然后发现自己太抽象了, 居然先转成 ArrayList
然后再排序, 可以直接使用 Arrays.sort()
, 因此简单改了一下代码
class Solution {
public long minimumRemoval(int[] beans) {
long result = 0;
Arrays.sort(beans);
int[] diff = new int[beans.length];
for (int i = 0; i < diff.length; i++) {
diff[i] = beans[i]-beans[0];
}
for (int i = 0; i < diff.length; i++) {
result += diff[i];
}
long tempResult = result;
for (int i = 0; i < beans.length-1; i++) {
tempResult += beans[i];
tempResult -= (beans[i+1]-beans[i])*(beans.length - i - 1);
if(tempResult<result){result = tempResult;}
}
return result;
}
}
成功优化到了 39ms, 并且击败了 46.99%。然后继续修修补补发现diff根本不需要,时间效率到了38ms, 但是感觉这个算误差了
class Solution {
public long minimumRemoval(int[] beans) {
long result = 0;
int n = beans.length;
Arrays.sort(beans);
for (int i = 0; i < n; i++) {
result += beans[i]-beans[0];
}
long tempResult = result;
for (int i = 0; i < n-1; i++) {
tempResult += beans[i];
tempResult -= (beans[i+1]-beans[i])*(n - i - 1);
result = tempResult < result ? tempResult: result;
}
return result;
}
}
此时的时间复杂度应该主要就是排序部分的 O(nlogn)
了,因此想要提高再提高时间效率就需要用空间去换了。可以尝试优化成 O(n) 的时间复杂度.