通过重复删除第一个和中间、第一个和最后一个或中间和最后一个元素来清空数组的最小成本

Minimum cost to empty an array by repeatedly removing first and middle, first and last, or middle and last elements

提问人:sn- 提问时间:4/3/2021 最后编辑:sn- 更新时间:3/20/2022 访问量:1078

问:

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

问题

给定一个偶数大小的数组,我们可以对数组执行任何操作,直到数组变为空,使总成本最小

操作:

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

注意:

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

例:

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

输出: 4

解释:

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

数组 算法 与语言无关

评论

0赞 Yuri Ginsburg 4/3/2021
偶数大小数组的中间元素是什么?
0赞 Yuri Ginsburg 4/3/2021
好的,它只需要被定义。如果两对或三对相等怎么办?gcd
0赞 sn- 4/3/2021
@YuriGinsburg你可以采取其中任何一个。除非总成本最低,否则这并不重要。
0赞 Yuri Ginsburg 4/3/2021
在这种情况下,您的算法使用什么策略?
2赞 n. m. could be an AI 4/4/2021
您描述的是一个贪婪的算法。贪婪的算法很少是最佳的。给定这个输入 {2,1000,1000,5},你能说服自己你的解决方案是最优的吗?

答:

4赞 גלעד ברקן 4/5/2021 #1

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

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

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

JavaScript 代码(此处提供 Python 中针对暴力破解的随机测试):

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));

评论

0赞 sn- 4/6/2021
+1 的答案和努力,但我做了一些试运行,例子很少。您的代码返回输入,而它应该返回 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。4[8,5,21,10,25,9]
0赞 גלעד ברקן 4/7/2021
@Kakashi我想我弄清楚了如何进一步降低状态。关键是这一行,以及我们看到 mid 等于 l1 或 r0 的下一行。这意味着这两个部分的长度可以相差 0 或 2。const _l1 = l1 - (mid == l1 ? 1 : 0);
0赞 גלעד ברקן 4/8/2021
@Kakashi,将搜索空间减少到 O(n^3)。面试官是否提供了解决方案的预期复杂性?
1赞 Anatolii 4/9/2021
@גלעדברקן你是对的。不需要额外的阵列。+1