2171 拿出最少数目的魔法豆(枚举 + 前缀和)

1. 问题描述:

给你一个整数数组 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

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/removing-minimum-number-of-magic-beans/

2. 思路分析:

因为需要使得剩余袋子的豆子数量相等所以只能够将当前其余袋子的豆子的数量与袋子中豆子数量最少的豆子数量相等,基于这个想法我们可以先对beans排序,我们可以枚举将前i个袋子的豆子拿掉,然后使得剩余袋子中豆子的数量相等,i从0~n-1,因为需要将前i个袋子的豆子拿掉所以需要维护beans的前缀和s2,并且需要求解使得剩余袋子的豆子数量相等求解对应的操作次数,我们可以维护前缀和s1,其中s1[i]表示前i个数字变为第一个数字的操作次数,这样每一个元素都是以第一个袋子的豆子为基准,这样后面我们使得第i个位置之后剩余袋子中的豆子数量相等的时候减去剩余袋子数量 * (beans[i + 1] - bean[0]),并且还需要减去前i个位置变为第一个位置豆子数量的操作次数,因为我们是直接拿掉前i个位置的豆子所以这一部分的值需要减掉,所以枚举拿掉前i个袋子的豆子使得剩余袋子中豆子数量相等操作次数为:s2[i+1] + s1[n-1] - s1[i] - (n-i-1)*(beans[i+1]-beans[0]),并且还需要特判一下特殊情况:将所有元素变为第一个元素的操作次数,所有情况取一个min就是答案。

3. 代码如下:

go:

package main

import (
	"fmt"
	"sort"
)

// 求解两个int数字的最小值
func getMin(a, b int) int {
	if a < b {
		return a
	}
	return b
}

func minimumRemoval(beans []int) int64 {
	sort.Ints(beans)
	n := len(beans)
	// 声明切片s1, s2这样不用声明数组那样需要固定长度, 其中s1[i]表示将0~i的数字变为第一个数字操作次数, s2[i]表示0~i-1的beans的前缀和
	s1, s2 := make([]int, n+10), make([]int, n+10)
	x := 0
	for i := 1; i < n; i++ {
		x += beans[i] - beans[i-1]
		s1[i] += s1[i-1] + x
		s2[i] += s2[i-1] + beans[i-1]
	}
	s2[n] += s2[n-1] + beans[n-1]
    // 特殊情况
	res := s2[n]-beans[0]*n
	for i := 0; i < n-1; i++ {
        // 枚举将前i个数字拿掉, 其余数字变为相等, 因为要变为相等肯定是将其余数字变为第i + 1个数字, 使用公式结合之前维护的s1, s2的值计算出其余数字变为bean[i + 1]的操作次数, 所有情况取一个min就是答案
		t := s2[i+1] + s1[n-1] - s1[i] - (n-i-1)*(beans[i+1]-beans[0])
		res = getMin(res, t)
	}
	return int64(res)
}

python:

from typing import List


class Solution:
    def minimumRemoval(self, beans: List[int]) -> int:
        n = len(beans)
        beans.sort()
        s1, s2 = [0], [0]
        x = 0
        for i in range(1, n):
            x += beans[i] - beans[i - 1]
            s1.append(s1[i - 1] + x)
            s2.append(s2[-1] + beans[i - 1])
        # 计算最后一个位置的前缀和
        s2.append(s2[-1] + beans[-1])
        res = s2[-1] - beans[0] * n
        for i in range(n - 1):
            res = min(res, s2[i + 1] - s1[i] + s1[n - 1] - (n - i - 1) * (beans[i + 1] - beans[0]))
        return res
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值