【前端也得会算法】2171. 拿出最少数目的魔法豆 [ 中等 ]

263 阅读3分钟

Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情

一、题目描述:

给你一个  整数数组 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 个魔法豆更少的方案。

二、题解:

方法一 取出比较法

  • 原理。每一种取出之和做比较。记录算过的值,算每一个数为留下的魔法豆的最小值即可
  • 思路。
    • 使用对象记录每一个留下的数,存在过则跳过,不存在且不为0则可以使用该值
    • 判断数组其余数字和当前留下数差值是否小于0,小于0则全拿出,否则则拿出多的部分即可

代码:

var minimumRemoval = function(beans) {
    // beans.sort((a,b) => a-b)
    let min = 0
    let obj ={}
    for(let i=0;i<beans.length;i++){
        let item = beans[i]
        if(!obj[item] && item){
            obj[item] = 1
            let tempSum = 0
            for(let j=0;j<beans.length;j++){
                const _bean = beans[j]
                if(!_bean || i===j){
                    continue
                }
                const diff = _bean-item
                if(diff<0){
                    tempSum += _bean
                } else if(diff>0){
                    tempSum += diff
                }
            }
            if(!min){
                min = tempSum
            }else{
                min = Math.min(tempSum,min)
            }
            
        }
    }
    return min
};

最后超时了,只能舍弃

方法二 留值最多法

  • 原理。取出值最小,即求留下值最多。
  • 思路。
    • 先排序,从小到大的顺序
    • 再求beans之和
    • 因从小到大了,假定第i个袋子为最合适的留下值,i之前则都不要,那么剩下的袋子都得为beans[i]的数目,即(beans.length-i)* beans[i]为留下最多的值
    • 再使用 beans之和减去留下的最多值,即为最小留下的值

代码:

var minimumRemoval = function(beans) {
    beans.sort((a,b) => a-b)
    let total =  beans.reduce((a,b) => a+b)
    let min = total
    for(let i=0;i<beans.length;i++){
        min = Math.min(min, total-beans[i] * (beans.length-i) )
    }
    return min
};

image.png

方法三 留值最多法2

  • 原理。取出值最小,即求留下值最多。
  • 思路。
    • 先使用函数得出最大值
    • 构造一个长度为dp+1的数组dp(因为最大数的下标比他小一,所以长度+1)
    • 将beans分布到dp上并且求和
    • 再使用 beans之和减去留下的最多值,即为最小留下的值

代码:

var minimumRemoval = function(beans) {
    const max = Math.max(...beans)
    const dp = new Array(max+1).fill(0)
    let _len = beans.length;
    let total = 0
    for(let item of beans){
        dp[item]++
        total+=item
    }
    let min = total
    for(let i=1;i<dp.length;i++){
        if(dp[i]>0){
            min = Math.min(min, total-_len*i )
            _len -= dp[i]
        }
    }
    return min
};

这个地方的_len*i等同于方案2的(beans.length-i)* beans[i],_len为剩下比i大的长度(beans.length-i),i为beans[i]

具体可参考下图: 示例1的beans = [4,1,6,5]

image.png

image.png

方案比较

方案二 时间复杂度O(nlogn)

方案三 时间复杂度可能为O(k?)k为最大数(这个时间复杂度该怎么算呢?)

三、总结

  • 此题可以取出比较法留值最多法两种方案
  • 取出比较法主要是每一种取出之和做比较。记录算过的值,算每一个数为留下的魔法豆的最小值即可。但由于这个题输入数据太多,算法时间复杂度过高,超出时间限制了
  • 留值最多法取出值最小,即求留下值最多。

文中如有错误,欢迎在评论区指正