【问题标题】:Minimum cost to empty an array by repeatedly removing first and middle, first and last, or middle and last elements通过重复删除第一个和中间、第一个和最后一个或中间和最后一个元素来清空数组的最小成本
【发布时间】:2021-06-29 20:25:04
【问题描述】:

这个问题是在一次采访中问我的,我无法想出一个最佳的解决方案。

问题

给定一个偶数大小的数组,我们可以对数组进行任意操作,直到数组变成为空,这样总的成本应该是最低

操作:

  1. 删除第一个和最后一个元素 => 成本将是 gcd(first, last)
  2. 删除第一个和中间元素 => 成本将为 gcd(first, middle)
  3. 删除中间和最后一个元素 => 成本将是 gcd(middle, last)

注意:

  • 在偶数大小的数组中,有两个中间元素。因此,将第二个中间元素视为中间元素。
  • gcd(x,y) 代表 x 和 y 之间的Greatest Common Divisor

示例:

输入:[2, 4, 8, 6]

输出:4

解释:

  • 在第一个操作中删除第一个和中间元素,cost = gcd(2,8) = 2
  • 在第二个操作中删除 4 和 6,cost = gcd(4,6) = 2
  • 总成本 = 2+2 = 4

我的方法是比较所有 3 个操作成本,以最低者为准,直到数组为空。而且,这没有用

【问题讨论】:

  • even 大小数组的 middle 元素是什么?
  • 好的,只需要定义。如果两三对的gcd相等怎么办?
  • @YuriGinsburg 您可以选择其中任何一个。除非总成本最低,否则没关系。
  • 你的算法在这种情况下使用什么策略?
  • 你在描述一个贪心算法。贪心算法很少是最优的。鉴于此输入 {2,1000,1000,5},您能否说服自己您的解决方案是最优的?

标签: arrays algorithm math language-agnostic


【解决方案1】:

无论我们执行操作的顺序如何,列表的状态都可以用四个指针来描述,这些指针准确地描述了列表中剩余的两个连续部分。

这些指针与一组特定的规则相关联,这些规则限制了可以实现的状态。我会从使用四个指针组合开始,并使用记忆状态进行递归。

我们可以通过观察两个连续部分的长度可以相差 0 或 2 来进一步减少状态。我稍后会尝试更新这个答案。

JavaScript 代码(Python 中针对暴力破解的随机测试可用here):

function gcd(x, y){
  while(y)
    [x, y] = [y, x % y];
  return x;
}

function f(A){
  const memo = {};
  const n = A.length;
  const mid = n / 2;
  
  function g(l0, l1, r0, r1){
    const key = String([l0, l1, r0, r1]);

    if (memo.hasOwnProperty(key))
      return memo[key];
    
    const len_l = l1 - l0 + 1;
    const len_r = r1 - r0 + 1;

    if (len_l + len_r == 2){
      if (len_r && len_l)
        return gcd(A[l0], A[r0]);
      else if (len_l)
        return gcd(A[l0], A[l0 + 1]);
      else
        return gcd(A[r0], A[r0 + 1]);
    }

    let a = A[l0];
    let b = A[r1];

    const outer = gcd(a, b) + g(l0 + 1, l1, r0, r1 - 1);

    if (len_r >= len_l)
      var mid = r0 + (len_l + len_r) / 2 - len_l;
    else
      var mid = l0 + (len_l + len_r) / 2;

    b = A[mid];
    
    const _l1 = l1 - (mid == l1 ? 1 : 0);
    const _r0 = r0 + (mid == r0 ? 1 : 0);

    const left = gcd(a, b) + g(l0 + 1, _l1, _r0, r1);

    a = A[r1];

    const right = gcd(b, a) + g(l0, _l1, _r0, r1 - 1);

    return memo[key] = Math.min(outer, left, right);
  }
  
  return g(0, mid - 1, mid, n - 1);
}

let A = [2, 4, 8, 6];
console.log(JSON.stringify(A));
console.log(f(A));

A = [8,5,21,10,25,9]
console.log(JSON.stringify(A));
console.log(f(A));

const n = 200;
A = [];

for (let i=0; i<n; i++)
  A.push(i + 1);

console.log(JSON.stringify(A));
console.log(f(A));

【讨论】:

  • +1 表示答案和努力,但我用几个例子做了一些试运行。您的代码为输入 [8,5,21,10,25,9] 返回 4 而它应该返回 3。 [8,5,21,10,25,9] => gcd(10,9) = 1, 剩余 [8,5,21, 25] => gcd(21,25) = 1,剩余 [8,5] => gcd(8,5) = 1。总计 = 3。
  • @Kakashi 我想我想出了如何进一步减少状态。关键是这一行,const _l1 = l1 - (mid == l1 ? 1 : 0);,接下来我们看到 mid 等于 l1 或 r0。这意味着这两个部分的长度可以相差 0 或 2。
  • @Kakashi 将搜索空间减少到 O(n^3)。面试官是否为解决方案提供了预期的复杂性?
  • @גלעדברקן 你是对的。不需要额外的数组。 +1
猜你喜欢
  • 2011-06-06
  • 1970-01-01
  • 1970-01-01
  • 2020-02-04
  • 1970-01-01
  • 2013-01-14
  • 2023-02-16
  • 1970-01-01
相关资源
最近更新 更多