【问题标题】:Find the maximum product that can be formed by taking any one element from each sub-array找出每个子数组中任意一个元素可以形成的最大乘积
【发布时间】:2020-07-31 11:30:49
【问题描述】:

我正在尝试用 JavaScript 编写一个高效的算法来解决这个任务。请看下一个输入数据和正确结果的例子:

Array: [ [-3,-4], [1,2,-3] ] Result: (-4)*(-3) = 12
Array: [ [1,-1], [2,3], [10,-100,20] ] Result: (-1)*3*(-100) = 300
Array: [ [-3,-15], [-3,-7], [-5,1,-2,-7] ] Result: (-15)*(-7)*1 = 105

它可以是任意数量的子数组和每个子数组中任意数量的元素。我已经发现,我可能应该在每个子数组中只保留最小值和最大值,我使用.map(a => [Math.min(...a), Math.max(...a)]) 完成并使用.sort((a, b) => a[0] - b[0]) 对它们进行排序。

现在我被困住了。可能有一种方法可以计算所有可能的产品,但我确信这不是解决此任务的有效方法。

请帮忙!

【问题讨论】:

  • 除非您有数以万计的元素或您发现了性能问题不要追求效率。这是浪费你的时间,它使代码更难维护。只需使用一对嵌套循环,尝试所有排列并跟踪最大值。

标签: javascript algorithm


【解决方案1】:

您发布的问题可以通过简单的算法来解决。我们只需要在迭代每个子数组时继续跟踪最大值/最小值。我们可以通过将当前最大值/最小值与每个子数组中的最大值/最小值相乘来继续找到下一个最大值/最小值。当迭代结束时,我们选择最大值。它的时间复杂度为O(n),其中n 是数组中的元素总数(即每个子数组中元素数的总和)。

这是完整的代码。 find_maximum_product 函数不断跟踪最小值/最大值并最终返回最大值,它还不断跟踪乘数并返回:

/**
 * arr: array of any number of sub-arrays and
 *      any number of elements in each sub-array.
 *      e.g. [[1, -1], [2, 3], [10, -100, 20]]
 */
function find_maximum_product(arr) {
  let max = 1;
  let min = 1;
  let max_multipliers = [];
  let min_multipliers = [];

  for (let i = 0; i < arr.length; i++) {
    const a = Math.max(...arr[i]);
    const b = Math.min(...arr[i]);

    const candidates = [max * a, max * b, min * a, min * b];
    max = Math.max(...candidates);
    min = Math.min(...candidates);

    let new_max_multipliers;
    let new_min_multipliers;

    switch (max) {
      case candidates[0]:
        new_max_multipliers = max_multipliers.concat(a);
        break;
      case candidates[1]:
        new_max_multipliers = max_multipliers.concat(b);
        break;
      case candidates[2]:
        new_max_multipliers = min_multipliers.concat(a);
        break;
      case candidates[3]:
        new_max_multipliers = min_multipliers.concat(b);
        break;
    }

    switch (min) {
      case candidates[0]:
        new_min_multipliers = max_multipliers.concat(a);
        break;
      case candidates[1]:
        new_min_multipliers = max_multipliers.concat(b);
        break;
      case candidates[2]:
        new_min_multipliers = min_multipliers.concat(a);
        break;
      case candidates[3]:
        new_min_multipliers = min_multipliers.concat(b);
        break;
    }

    max_multipliers = new_max_multipliers;
    min_multipliers = new_min_multipliers;
  }

  if (max >= min) {
    return [max, max_multipliers];
  }
  return [min, min_multipliers];
}

const arrays = [
  [
    [-3, -4],
    [1, 2, -3],
  ],
  [
    [1, -1],
    [2, 3],
    [10, -100, 20],
  ],
  [
    [-3, -15],
    [-3, -7],
    [-5, 1, -2, -7],
  ],
  [
    [14, 2],
    [0, -16],
    [-12, -16],
  ],
  [
    [-20, -4, -19, -18],
    [0, -15, -10],
    [-13, 4],
  ],
  [
    [-2, -15, -12, -8, -16],
    [-4, -15, -7],
    [-10, -5],
  ],
];

for (let i = 0; i < arrays.length; i++) {
  const [max, max_multipliers] = find_maximum_product(arrays[i]);
  console.log('Array:', JSON.stringify(arrays[i]));
  console.log('Result:', `${max_multipliers.join(' * ')} = ${max}`);
  console.log('');
}

更新

只获得最大值而不是乘数的更简单的版本:

/**
 * arr: array of any number of sub-arrays and
 *      any number of elements in each sub-array.
 *      e.g. [[1, -1], [2, 3], [10, -100, 20]]
 */
function get_maximum_product(arr) {
  return arr
    .map((a) => [Math.min(...a), Math.max(...a)])
    .reduce(
      (acc, current) => {
        const candidates = [
          acc[0] * current[0],
          acc[0] * current[1],
          acc[1] * current[0],
          acc[1] * current[1],
        ];
        return [Math.min(...candidates), Math.max(...candidates)];
      },
      [1, 1]
    )[1];
}

【讨论】:

  • 很好的答案 (+1)。这与我的想法一致,但没有完全正确(我选择了错误的重复目标,专注于正负而不是纯粹的最大值和最小值)。
【解决方案2】:

这是一个自上而下的循环,可以适应自下而上(循环)并利用 O(n) 搜索空间。

在我完成之前,鼓励读者在元组中添加第三个返回值,largest_non_positive 用于该特殊情况。

// Returns [highest positive, lowest negative]
// Does not address highest non-positive
function f(A, i){
  const high = Math.max(...A[i]);
  const low = Math.min(...A[i]);

  if (i == 0){
    if (low < 0 && high >= 0)
      return [high, low];
    if (low <= 0 && high <= 0)
      return [-Infinity, low];
    if (low >= 0 && high >= 0)
      return [high, Infinity];
  }

  const [pos, neg] = f(A, i - 1);
  
  function maybeZero(prod){
    return isNaN(prod) ? 0 : prod;
  }

  let hp = maybeZero(high * pos);
  let hn = maybeZero(high * neg);
  let ln = maybeZero(low * neg);
  let lp = maybeZero(low * pos);

  if (low < 0 && high >= 0)
    return [Math.max(hp, ln), Math.min(hn, lp)];

  if (low <= 0 && high <= 0)
    return [ln, lp];

  if (low >= 0 && high >= 0)
    return [hp, hn];
}

var As = [
  [[-3,-4], [1,2,-3]],
  [[1,-1], [2,3], [10,-100,20]],
  [[-3,-15], [-3,-7], [-5,1,-2,-7]],
  [[-11,-6], [-20,-20], [18,-4], [-20,1]],
  [[-1000,1], [-1,1], [-1,1], [-1,1]],
  [[14,2], [0,-16], [-12,-16]],
  [[-20, -4, -19, -18], [0, -15, -10],[-13, 4]]
];

for (let A of As){
  console.log(JSON.stringify(A));
  console.log(f(A, A.length - 1)[0]);
  console.log('');
}

【讨论】:

  • 请尝试使用下一个数组的解决方案:[ [14,2], [0,-16], [-12,-16] ]。结果应该是:14*(-16)*(-16)=3584。
  • 感谢您的回答。我将详细探索您的方式并尝试解决其他特殊情况。现在,我发现了以下情况: [ [ -20, -4, -19, -18 ], [ 0, -15, -10 ], [ -13, 4 ] ] 和 [ [ -2, -15, -12, -8, -16 ], [ -4, -15, -7 ], [ -10, -5 ] ]。
  • @oleksiisedun 我最近的编辑地址是 [ [ -20, -4, -19, -18 ], [ 0, -15, -10 ], [ -13, 4 ] ] 吗?最高非正解的最后一个似乎微不足道。如果我有时间,我会尝试将第三个返回值 largest_non_positive 添加到可以解决该问题的元组中。
【解决方案3】:
  1. 按绝对值降序对数组中的值进行排序
  2. 检查第一个数组元素的乘积是否为正
  3. 否则我们将产品称为p,我们知道p &lt; 0,所以如果我们将一些正面元素更改为一些负面元素或反之亦然,我们将改进答案
  4. 我们可以简单地检查所有可能要更改的元素,对于每个数组 a 元素 x 我们可以检查 p / a[0] * x 是否比当前结果更好,如果我们更新我们的答案
  5. *特殊情况:数组中的所有元素都是负数,我们有奇数个数组,那么我们就简单地按升序排序

复杂度:O(n log n) 其中n 是所有数组中元素的总数

【讨论】:

    【解决方案4】:
    1. 取所有至少有一个正数的数组中最大数的乘积。

    2. 如果剩余数组的数量为奇数(只有负数),则找到负数最大(最接近于零)的数组,并将其绝对放在一边。

    3. 取第 2 步后剩余的数组,取其最小数(离零最远)的乘积,然后乘以第 1 步和第 2 步(如果有的话)的乘积。

    (另外,避免0,如果它是选择的号码)

    【讨论】:

    • 请您将您的算法逐步应用于下一个数组:[ [4, -7], [-8, 3] ]。
    • 这听起来不太理想。考虑以下数组列表:[[-1000, 1], [-1, 1], [-1, 1], [-1, 1]]。每个数组至少有一个正数,因此您的算法将选择乘积 1*1*1*1,而不是使用 (-1000)*(-1) 来获得更高的结果。
    【解决方案5】:

    首先要注意的是,只有两种特定情况是不可能获得正面产品的。所以我认为算法应该首先检查这些特定情况是否正在发生,然后为三种可能的情况中的每一种调用不同的子算法:

    1. 有可能得到正积,所以我们要找到正积最高的;
    2. 其中一个数组全为零,所以所有乘积都为零;
    3. 不可能得到正积,因为没有一个数组既有正数又有负数,而且只有负数的数组有奇数个,所以我们要找到最接近零的负积。

    第二种和第三种情况导致了琐碎的算法。

    让我们考虑第一种情况。

    对于每个数组,在最高乘积中唯一有用的数字是最大的正数和最小的负数。如果一个数组只有正数或只有负数,那么该数组中只有一个有用的数,可以立即选择。

    对于所有剩余的数组,您必须选择使用正数还是负数。理想情况下,您希望使用绝对值最高的那个;但是如果你对每个数组都这样做,那么结果可能是负数。

    这导致了一个线性算法:

    1. 对于所有剩余的数组,首先选择绝对值最高的数字
    2. 如果生成的产品是阳性的,那么您就完成了。
    3. 如果结果是负数,则必须在其中一个阵列中进行折衷。对于每个数组,计算这种折衷的“成本”(等于该数组中两个有趣数字的绝对值之差乘以所有其他选定数字的乘积)。
    4. 最后,选择成本最低的数组,并更改该数组中选择的数字。

    这是在数组列表[[18,19,20,-23], [12,-10,9,8],[-10,-3],[5,3],[-10,-5]]上执行算法的示例。

    在这里我们注意到可以找到正解,因为至少有一个数组包含负数和正数。

    对于最后三个数组,我们无法在正负之间进行选择:因此我们已经可以选择 -10、5 和 -10 作为这三个数组的三个数字。对于第一个数组,我们必须在 20 和 -23 之间进行选择;对于第二个数组,我们必须在 12 和 -10 之间进行选择。

    所以最终产品将是:(20 or -23) * (12 and -10) * (-10) * 5 * (-10)

    理想情况下,我们更喜欢 23 到 20,以及 12 到 10。这将导致: (-23) * 12 * (-10) * 5 * (-10)

    不幸的是,这是负面的。所以问题是:我们是用 20 代替 -23,还是用 -10 代替 12?

    将 -23 替换为 20 的成本为 (23-20) * 11 * (10*5*10) = 33 * (10*5*10)

    将 12 替换为 -10 的成本为 (12-10) * 21 * (10*5*10) = 42 * (10*5*10)

    最后,我们选择将 -23 替换为 20,因为这是成本更低的折衷方案。

    最终产品是20 * 12 * (-10) * 5 * (-10)

    【讨论】:

      猜你喜欢
      • 2019-10-24
      • 2021-05-09
      • 2020-09-06
      • 2017-02-02
      • 1970-01-01
      • 1970-01-01
      • 2015-08-15
      • 1970-01-01
      • 2020-08-30
      相关资源
      最近更新 更多