[ CodeWar ] - 007:找不同

357 阅读5分钟

系列文章

题目

这是一个系列题目,我们先来看看第一题:

img-01

需求:

  • 传入的数组中除了一项以外,别的值都相等
  • 找到这个与其他不同的值

解析

这是这个系列的第一题,思路非常简单:

  • 将数组排序,那么特殊的值要么在首,要么在尾
  • 如果 arr[0] === arr[1] 则返回 arr.pop()
  • 否则返回 arr.[0] 即可

按照上面的思路实现:

const findUniq = (arr) =>
  arr.sort()[0] === arr.sort()[1]
    ? arr.sort()[arr.sort().pop()]
    : arr.sort()[0];

进阶

上面的思路已经非常简单,没必要再优化,所以我们接下来看看下一题:

img-02

相比于第一题,这里仅仅是将数组的元素换成了字符串,但实际上难度缺是实实在在的提升了:至少这里没法通过排序的方式来取巧了。

思路

我们来梳理一下题目中蕴含的条件:

  • (原题下方提示了,数组长度最少为 3,所以这里就不考虑数组长度 < 3 的情况)
  • 数组元素为字符串,大小写不确定,长度不确定,存在重复字符串
  • 需要找出去重之后,大写/小写之后与别的字符串不同的一项

条件其实不多,并且每个元素也相对比较复杂,我们这里来捋一捋思路:

  • 对于每个元素,我们可以将其转化成小写,去重,这样就是我们将要用来比较的最终元素了
  • 而对于乱序字符串的比较,我们可以转化成数组,利用 sort() 排序,再转回字符串进行比较

结合上述两点,思路就比较清晰了:

function findUniq(arr{
  let curStr, nxtStr;
  for (let index = 0; index < arr.length - 1; index++) {
    curStr = [...new Set(arr[index].toLocaleLowerCase().split(""))]
      .sort()
      .join("");
    nxtStr = [...new Set(arr[index + 1].toLocaleLowerCase().split(""))]
      .sort()
      .join("");
    if (curStr !== nxtStr) {
      return arr[index + 1];
    }
  }
}

上面的代码看起来似乎没什么问题,但在实际测试中发现,我们少考虑了一个情况:

  • 如果是数组的第一项与之后的项不同,这里返回的是数组的第二项

知道问题在哪儿,修复也就很简单了,添加一个条件判断即可:

function findUniq(arr{
  let firStr, secStr, thrStr, curStr, nxtStr;
  firStr = [...new Set(arr[0].toLocaleLowerCase().split(""))].sort().join("");
  secStr = [...new Set(arr[1].toLocaleLowerCase().split(""))].sort().join("");
  thrStr = [...new Set(arr[2].toLocaleLowerCase().split(""))].sort().join("");
  if (firStr !== secStr && firStr !== thrStr) {
    return arr[0];
  }
  for (let index = 0; index < arr.length - 1; index++) {
    curStr = [...new Set(arr[index].toLocaleLowerCase().split(""))]
      .sort()
      .join("");
    nxtStr = [...new Set(arr[index + 1].toLocaleLowerCase().split(""))]
      .sort()
      .join("");
    if (curStr !== nxtStr) {
      return arr[index + 1];
    }
  }
}

优化

当然,上面的代码虽然实现了功能,但是逻辑是比较复杂的,那么这里我们从什么角度进行优化呢?

最简单的,首先我们可以把对比功能抽离出来,逻辑就会清晰很多:

function similar(cur, nxt{
  cur = [...new Set(cur.toLocaleLowerCase().split(""))].sort().join("");
  nxt = [...new Set(nxt.toLocaleLowerCase().split(""))].sort().join("");
  return cur !== nxt ? false : true;
}

function findUniq(arr{
  let [fir, sec, thr] = arr.slice(03);
  if (!similar(fir, sec) && !similar(fir, thr)) return fir;
  for (const cur of arr) if (!similar(fir, cur)) return cur;
}

这里我们再来尝试一下别的思路:

  • 由于我们的目的是找到数组中的某个元素,所以在返回的时候,直接返回 arr[x] 即可
  • 如上,现在我们关注的点是 x 的值
  • 首先我们可以通过 map() 将数组中的值转化成方便比对的形式
  • 然后我们可以借助 findIndex() 来找到这个 x
  • 经过前面的分析我们已经知道(这里说的前面是包括前面的实现),数组经过转化之后,里面的元素已经不区分大小写,顺序相同,并且没有重复,形成了类似 ["a", "a", "b", "a"...] 的形式
  • 那么我们只要找到某个元素,它在数组中第一次和最后一次在数组中出现的位置相同,那么它一定是唯一的

综合上述思路,我们的代码就比较好实现了:

const findUniq = (arr) =>
  arr[
    arr
      .map((x) => [...new Set([...x.toLowerCase()].sort())].join(""))
      .findIndex((x, i, ar) => ar.indexOf(x) === ar.lastIndexOf(x))
  ];

当然,同样的思路,还能写的更骚一些:

findUniq = (
  a,
  b = a.map((a) => [[...new Set(a.toLowerCase())].sort().join``, a]),
  c = b.map((a) => a[0])
) => b[c.findIndex((a) => c.indexOf(a) == c.lastIndexOf(a))][1];

进阶

img-03

其实在实现了