探索JavaScript中的对称性差异

133 阅读4分钟

简介

对称差分面试题是一个有趣的问题,因为如果你能想到使用Set对象,那么它的解决就会相对简单,否则就会显得非常具有挑战性或效率低下。

对称差分问题

首先,让我们了解一下对称差分问题。它通常是以下面的某种形式提出的。

要求你创建一个函数,找出任意数量的数组的对称差。两个数组的对称差是通过找到所有在一个数组中但不在另一个数组中的值来计算的。例如,[1, 2, 3][2, 3, 4] 的对称差是[1, 4] ,因为数字14 只在这两个数组中的一个。重要的是,输出数组应该只有唯一的值。

当你有两个以上的数组时,对称差是从左到右计算的,将下一个数组与前两个数组的对称差进行比较。例如,[1, 2, 3],[2, 3, 4], 和[3, 4, 5] 的对称差值将被计算如下。

  • [1, 2, 3][2, 3, 4] 的对称差是[1, 4]
  • [1, 4][3, 4, 5] 的对称差是[1, 3, 5]

因此,答案是[1, 3, 5]

问题设置

根据问题的描述,我们的函数描述可能是这样的。

/**
 * @param {number[][]} arrs
 * @return {number[]}
 */
const symDiff = (arrs) => {
  // Solution here
};

其中arrs 是一个数字数组,我们的输出是一个数字数组。

关于在面试中解决编码难题的简要说明

如果你在面试中解决任何编码挑战,在开始解决问题之前,最好先问一些澄清的问题。在对称差分案例中,你可能想问以下问题(可能还有一些我想不起来的问题)。

  • 输入可以是零数组吗?如果是这样,那么在这种情况下的对称差是什么?
  • 输入可以是一个数组吗?同样,在这种情况下的对称性差异是什么?
  • 输入的数组可以包含数字以外的东西吗?如果可以,请说明非数字情况下的行为。

在这篇博文中,我们将假设输入数组总是两个或多个数字数组。

一个成语式的JavaScript解决方案

让我们直接进入正题:下面的片段显示了一个习惯性的JavaScript解决方案,它结合了Set 对象、reduce 数组方法、三元组运算符和扩散运算符等概念。

const symDiff = (arrs) => {
  arrs[0] = new Set(arrs[0]);
  const diff = arrs.reduce((acc, cur) => {
    const prevNums = new Set();
    cur.forEach((el) => {
      if (prevNums.has(el)) return;
      acc.has(el) ? acc.delete(el) : acc.add(el);
      prevNums.add(el);
    });
    return acc;
  });
  return [...diff];
};

这里真正的主角是Set 对象。让我们深入了解它是如何工作的。

它是如何工作的

了解其工作原理的最好方法是逐行浏览。我将在前面的代码中加上注释,解释每一行。

const symDiff = (arrs) => {
  /*
  Mutates the first element of the input array 
  to make it a `Set` object. (Note: it's not 
  necessarily prudent to mutate your input array, 
  but we could ask the interviewer if that's 
  allowed and pivot if it's not).
  */
  arrs[0] = new Set(arrs[0]);
  /*
  Reduce over our input array. The accumulator 
  (acc) will be start out as our Set above and 
  then, in each subsequent iterator, will be the 
  result of the previous symmetric difference!
  */
  const diff = arrs.reduce((acc, cur) => {
    /* 
    Create a Set to track if what numbers have 
    already appeared in the current (cur) array
    */
    const prevNums = new Set();
    /*
    Iterate through each element in the current 
    array so we can check if it's in the 
    accumulator array.
    */
    cur.forEach((el) => {
      /*
      If this number has already shown up in the 
      current array, skip it
      */
      if (prevNums.has(el)) return;
      /*
      If the accumulator contains the current 
      number, then it is in both arrays and cannot 
      be in the symmetric difference. So, delete it 
      from the accumulator. On the other hand, if 
      the current number isn't in the accumulator, 
      it is in the symmetric difference, so add it.
      */
      acc.has(el) ? acc.delete(el) : acc.add(el);
      /*
      Take note that this number has been processed 
      for the current array to make sure we don't 
      evaluate a duplicate value in the future.
      */
      prevNums.add(el);
    });
    /*
    We now have our symmetric difference of the 
    accumulator and the current array! Return the 
    accumulator for evaluation with the next array 
    in line (or to return it from the reduce method 
    if we're done iterating through the arrays)
    */
    return acc;
  });
  /*
  Our output has to be an array, so spread the `diff` 
  set into a new array and return it. Could have 
  alternatively used `Array.from`.
  */
  return [...diff];
};

总结

我喜欢这个解决方案有几个原因。它似乎有相当好的时间复杂性,因为它需要精确地迭代一次输入数组,并精确地迭代一次每个子数组。此外,它为你提供了一个展示Set 对象知识的机会,并讨论为什么使用它是有益的(即,它具有查找元素的哈希表效率)。