【问题标题】:Minimize array sum after K operations of `ceil(A[i] / 2)`在 `ceil(A[i] / 2)` 的 K 次操作后最小化数组总和
【发布时间】:2020-12-23 09:28:12
【问题描述】:

今天早上我没能解决这个面试问题。

给定一个整数数组,执行一些k操作,每个操作包括删除一个元素将其除以2计算ceil 然后用新元素替换该元素。

最后,最小化最终数组的结果

示例:

nums=[10,20,7]
k=4

最终的数组将是:

[5,5,4] 

求和结果:

5+5+4=14

场景:

初始数组:[10,20,7]

选择7[10,20,4]

选择10[5,20,4]

选择20[5,10,4]

选择10[5,5,4]

结果:

5+5+4 = 14

我的代码(未完成的是):

function minSum(num, k) {
    return num.map(value => Math.ceil(value / 2)).reduce((value, acc) => acc + value, 0)
}

console.log(minSum([10, 20, 7]))

让我感到困惑的是:

• 我们如何根据操作的数量来选择元素?

编辑:

因为我收到了 2 个关闭请求,让我澄清一下:

假设我们从索引0 到最后一个索引中选择元素,我的代码中缺少什么来满足k 操作?

【问题讨论】:

  • 为什么拣货单是7 -> 10 -> 20 -> 10?为什么不从数组的开头开始?如果k > array.length,在所有元素都被选中一次后会发生什么?为什么在你的情况下index = 1 被选中了两次?
  • 这就是让我超越时间的原因,为什么这个顺序,或者换句话说,选择元素的策略是什么!从leftright 或从rightleft 或随机?
  • 对于从 1 到 k 的每次迭代,您能否找到数组中的最大数并将其除以 2 并取 ceil。所以每次迭代你都会找到除以 2 的最大数(我认为这将是减少每次迭代的数组总和的最佳方法)(可能是这样的:jsfiddle.net/cut18Ljd) - 不太确定是否这将始终有效,需要多考虑一下
  • 他们只是展示了一种达到最低金额的方法。一旦您有任何 种方法来达到最小总和,您就可以随意更改操作的顺序,并获得相同的结果。例如,他们选择的顺序是索引 2,0,1,1。但他们可以选择 0,1,1,2 或 2,1,0,1 或任何其他顺序。容易找到的顺序是 1,1,0,2 或 1,0,1,2。我会让你弄清楚为什么这些是简单的答案。
  • @NickParsons 这绝对是我解决问题的方法。它之所以有效,是因为如果将除最大值以外的任何值减半,则数组的总和将减少到比您可能拥有的要少的程度。因此,您将减少 20,即 10、10 和 7 之一作为 4 次操作。

标签: javascript arrays algorithm


【解决方案1】:

将所有元素放入最大二进制堆中,即priority queue。 (随意找到JS implementation

进行 k 步 - 弹出最顶部(最大)的元素,将其除以 2,将结果推回队列中。

最后计算元素的总和。

这种(有点贪心的)方法给出了最小的总和结果。

它提供了非常好的时间复杂度O(n) + O(k*log(n)):这很重要,因为你在 cmets 中写了what makes me exceed the time

【讨论】:

    【解决方案2】:

    On 选项是对sort 数组按降序排列并更新数组中的第一项。

    let nums = [10, 20, 7],
        k = 4;
    
    while (k--) {
      nums.sort((a, b) => b - a)
      nums[0] = Math.ceil(nums[0] / 2)
    }
    
    const sum = nums.reduce((acc, cur) => acc + cur, 0)
    console.log(sum)

    如果数组很大,每次迭代中的排序可能效率低下。您实际上不需要在每次迭代中 sort 数组。您只需将 操作 nums[0] 的数字移动到它在排序数组中的新位置。

    let nums =  [10, 20, 7],
        k = 4;
    
    nums.sort((a, b) => b - a)
    
    while (k--) {
      nums[0] = Math.ceil(nums[0] / 2);
      
      // move nums[0] in the sorted array
      for (let i = 1; i < nums.length; i++) {
        if (nums[i] > nums[i-1])
          [nums[i-1], nums[i]] = [nums[i], nums[i-1]] // swap
        else
          break
      }
    }
    
    const sum = nums.reduce((acc, cur) => acc + cur, 0)
    console.log(sum)

    【讨论】:

      【解决方案3】:

      您可以为k 创建一个循环,对于从 1 到 k 的每次迭代,您可以找到数组中的最大数,然后将其除以 2 并取 ceil。当您在 minimum 总和之后,您希望减少数组中较大的数字。

      我认为Nick 在上面的评论中很好地表达了这一点:

      如果你将除最大值之外的任何值减半,你将减少 数组的总和小于你可以拥有的东西

      请看下面的例子:

      const nums=[10,20,7];
      const k = 4;
      
      for(let i = 0; i < k; i++) {
        const max = Math.max(...nums);
        const j = nums.indexOf(max);
        nums[j] = Math.ceil(nums[j] / 2);
      }
      
      const sum = nums.reduce((t, n) => t+n, 0);
      console.log(nums);
      console.log(sum);

      复杂度为O(n*k)k 迭代的 n*k 术语,其中每次迭代您在 n 数组上执行 nums 迭代以找到最大值。虽然没有反映在 Big-Oh 表示法中,但内部循环可以进行 2*n 次迭代。如果您使用诸如.findIndex() 之类的方法,这可以减少到仅n,这有助于提高整体性能。

      【讨论】:

        【解决方案4】:

        我想我明白了。

        “pick K”的意思是:在输入数组中找到值为 K 的元素,然后对该元素执行半舍入操作。

        如果是这样,那就很简单了。

        我们将使用map 来生成输出数组,但迭代器函数将具有对 K 敏感的分支行为。

        let initialArray = [ 10, 20, 7 ]
        
        function pick( k, inputArray ) {
            let outputArray = inputArray.map(v => {
                return (v === k)
                    ? Math.ceil(v/2)
                    : v
            })
            
            return outputArray
        }
        

        然后我们链接几次:

        let picks = [7, 10, 20, 10]
        let finalArray = picks.reduce(( currentArray, thisK ) => {
            return pick(thisK, currentArray)
        }, initialArray)
        

        最后,我们用 reduce 求和:

        let total = finalArray.reduce(( sum, value ) => sum + value, 0)
        

        说明中不清楚的一点是,如果 k 值在输入数组中多次出现,pick 应该如何表现。这里的版本将对所有匹配的值进行操作。但我想一些挑剔的面试官可能会期望你将其解释为“每次只修改第一个匹配项”。如果这是一个真实的场景,您必须询问业务代表以准确确定所需的内容,这就是我在面试中会做的事情。他们评估的部分内容可能是您如何处理模棱两可的技术要求。

        【讨论】:

        • 但是对于 OP 提供的 k=4 的示例输入,这似乎不会产生 [5, 5, 4] 的正确输出
        • k 是从0 开始的操作总数。第一个元素是7k的初始值为0。我有这个想法。好吧,因为我们将计算所有元素的 ceil,无论您是从开始还是结束开始,但第二个选择10 的困惑。
        • @NickParsons,假设我们从一开始就选择元素,我在代码中错过了什么以满足k 的数量?
        • @NickParsons 我明白了,但在我看来,这些例子似乎是矛盾的。你引用的第一个例子对我来说根本没有意义。另一组例子,显然是一个系列,似乎确实按照我推测的方式运作。我想我必须假设 OP 在某处有错字,而我的假设只需要一个错误。但我真的不知道。
        • @Tom 只有一个例子。数组是[10,20,7]k 是4。答案是5+5+4=14,因为最终数组是[5,5,4]
        猜你喜欢
        • 2021-01-16
        • 1970-01-01
        • 1970-01-01
        • 2018-09-16
        • 2014-12-29
        • 1970-01-01
        • 2021-10-03
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多