【问题标题】:What is the time complexity of this JS algorithm这个JS算法的时间复杂度是多少
【发布时间】:2018-12-11 08:51:24
【问题描述】:

IsUnique:实现一个算法来确定一个字符串是否具有所有唯一字符。

在我的一个版本中,我正在使用 RegExp。谁能给我解释一下这个算法的时间复杂度是多少?为什么?

const isUniqueV2 = function isUniqueV2(str) {
  const cleanStr = str.toLowerCase().replace(/[^a-z0-9]/g, '');
  const strlen = cleanStr.length;
  if(!strlen) return false;
  const reg = new RegExp(/(.)[^\1]*?\1/g);
  if(reg.test(cleanStr)) return false;
  return true;
}

RegExp 的时间复杂度因实现而异。我有一个 O(N) 的版本。我只是想知道这个会比使用字典 O(N) 的表现更好吗?

【问题讨论】:

  • 这个问题似乎根本没有包括任何解决问题的尝试。 StackOverflow 期待您 try to solve your own problem first,因为您的尝试有助于我们更好地了解您想要什么。请编辑问题以显示您尝试过的内容,并使用Minimal, Complete, and Verifiable example 显示您遇到的特定障碍。欲了解更多信息,请参阅How to Ask
  • 无论你在做什么都是 o[1],你不会循环任何元素,但我不确定 RegExp 是如何工作的,它必须做一些循环,但我确定如果 n 是 str 的长度,它的效率不应超过 o[n]
  • @OmarMneimneh 即使该模式需要引擎迭代每个字符的次数与字符串的长度成正比?最坏的情况,它需要 0.5 * L^2 步骤左右的东西
  • @Andreas 我已经解决了这个问题是 4 个版本。我对其余解决方案的时间复杂性有一个清晰的认识,但是在这个版本中,我使用了 Regex,我感到困惑。我不知道如何计算 RegExp 的时间复杂度。
  • 我很确定它是线性的。我们有toLowerCase(),它是O(n),我们有replace(),它是O(n),还有test(),它也是O(n)。我没有看到任何 O(n^2)...

标签: javascript regex performance time-complexity


【解决方案1】:

从技术上讲,在最坏的情况下,算法的时间复杂度将为O(N),但为什么它是O(N) 有点复杂。需要考虑三个操作。

首先,输入字符串上的toLowerCase()。就字符串的长度而言,这是O(N),很简单。

二、第一个.replace函数:.replace(/[^a-z0-9]/g, '')。这也是O(N) - 遍历所有字符,并将非字母数字字符替换为空字符串。

第三个也是最复杂的:/(.)[^\1]*?\1/g 测试。让我们先break down 这个正则表达式。请注意,字符集中的\1 可能不会像您认为的那样 - 它不是反向引用,它匹配索引 1 处的 unicode 字符,即 Start of Heading 控制字符:

console.log(/[\1]/.test(String.fromCharCode(1)));
console.log(String.fromCharCode(1));
// not the sort of thing that would appear in an ordinary string, as you can see

这不是你想要的。为了简单起见,让我们解决这个问题 - 它不会对您的算法的复杂性产生任何影响,因此我们假设我们使用的是 /(.).*?\1/ 模式。

它将捕获组中的第一个字符,然后懒惰重复任何字符,尝试再次找到与第一个组匹配的字符。正则表达式引擎将从字符串中的第一个字符开始尝试此测试 - 如果字符串长度为N,它将从索引0 开始并迭代索引0N - 1,检查是否有任何字符匹配索引0 处的字符。由于我们假设最坏的情况,我们可以假设这将失败(没有找到第一个字符的重复项),并且我们已经执行了大约 N 次操作。然后,引擎将尝试从 next 索引开始匹配,索引1,并将遍历每个后续字符,直到它到达字符串的末尾 (N - 1),寻找与索引 1 匹配的相同字符。最坏的情况下,这将失败,我们刚刚执行了 N - 1 操作,引擎将前向跟踪 另一个 字符,到索引 2。见模式?

Starting index     ~Operations required to check this index    ~Total operations
0                  N                                           N
1                  N-1                                         2N-1
2                  N-2                                         3N-3
3                  N-3                                         4N-6
4                  N-4                                         5N-10
...
N-1                1                                           N^2 - 0.5N^2

在最坏的情况下,字符串中没有重复的字符,并且引擎围绕0.5N^2 步骤来执行整个.test 函数。这并不准确,因为有一些overhead 与匹配捕获的字符相关,但与N^2 因素相比,它微不足道。在regex101 上尝试一下 - 你可以看到输入 62 个字母数字字符需要 2203 步,这与0.5 * 62^2 = 1922 相差不远。

所以,因为这个.test 函数在最坏的情况下具有O(N^2) 复杂度,所以听起来整个算法具有O(N^2) 复杂度,对吧?其实,不!原因是第一个.replace(/[^a-z0-9]/g, '') 确保正在测试的字符串将包含 小写字母和数字(36 个可能的字符)。这意味着 .test 在返回 true 之前最多只能迭代 36 个字符 - 第 37 个字符(或之后的任何字符)必然是前面字符之一的副本,因为只有 36 个可能的唯一字符。最坏情况的字符串看起来像

0123456789abcdefghijklmnopqrstuvwxyzzzzzzzzzzzzzzzzzzzzzz...

这需要大约36N 步骤才能到达zs,发现它们是重复的,然后传递.test。所以,.test 的最坏情况,考虑到受限输入,实际上是 O(N),而不是 O(N^2)

总结一下:在最坏的情况下,toLowerCase()O(N)。在最坏的情况下,.replaceO(N)。最后,.test 在最坏的情况下是O(N)。因此,在最坏的情况下,您的函数的时间复杂度为 O(N)

说了这么多,虽然它可能是O(N),但与您的other 实现相比,它仍然相对低效,它遍历字符串中的每个字符并将其添加为对象的属性,返回一次true找到任何重复的字符。

【讨论】:

    猜你喜欢
    • 2015-06-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-03
    • 2016-10-30
    相关资源
    最近更新 更多