【问题标题】:Find a duplicate in array of integers在整数数组中查找重复项
【发布时间】:2018-07-28 05:29:07
【问题描述】:

这是一道面试题。

我得到了一个 n+1 范围内的整数数组 [1,n]。数组的属性是它有k (k>=1)重复,每个重复可以出现两次以上。任务是在尽可能好的时间和空间复杂度中找到多次出现的数组元素。

在经历了巨大的挣扎之后,我自豪地提出了占用O(1) 空间的O(nlogn) 解决方案。我的想法是将范围[1,n-1] 分成两半,并确定两半中哪一个包含更多来自输入数组的元素(我使用的是鸽洞原理)。该算法递归地继续,直到达到间隔[X,X],其中X 出现两次,即重复。

面试官很满意,但后来他告诉我,存在O(n) 常数空间的解决方案。他慷慨地提供了一些提示(与排列有关的东西?),但我不知道如何提出这样的解决方案。假设他没有说谎,任何人都可以提供指导吗?我已经搜索过,发现这个问题很少(更容易)变体,但不是这个特定的变体。谢谢。

编辑:为了让事情变得更复杂,面试官提到不应该修改输入数组。

【问题讨论】:

  • 你不能把所有整数放在一个映射中,数字作为键,出现作为值,然后遍历所有键,我认为这将是 O(n),但也是 O(n ) 空间。
  • @maraca 至少是O(n) 空间。
  • 啊,我明白了,如果没有重复项,并且如果已经存在具有该值的元素,那么您可以通过在正确位置插入元素来进行排序,它应该让您找到重复项。
  • 如何在 O(n) 时间和 O(1) 空间中排序?
  • 连一点可逆修改都没有? (比如让一个元素变成负数)?

标签: arrays algorithm time-complexity big-o space-complexity


【解决方案1】:
  1. 取最后一个元素 (x)。

  2. 将元素保存在位置 x (y)。

  3. 如果 x == y,您发现了重复项。

  4. 用 x 覆盖位置 x。

  5. 分配 x = y 并继续步骤 2。

您基本上是在对数组进行排序,这是可能的,因为您知道必须在哪里插入元素。 O(1) 额外空间和 O(n) 时间复杂度。你只需要小心索引,为简单起见,我假设第一个索引在这里是 1(不是 0),所以我们不必做 +1 或 -1。

编辑:不修改输入数组

这个算法是基于我们必须找到置换循环的入口点的想法,然后我们也找到了一个重复的(为了简单起见还是从1开始的数组):

例子:

2 3 4 1 5 4 6 7 8

条目:8 7 6

排列周期:4 1 2 3

我们可以看到重复的 (4) 是循环的第一个数字。

  1. 寻找排列循环

    1. x = 最后一个元素
    2. x = 位置 x 的元素
    3. 重复步骤2。n次(总共),这保证我们进入了循环
  2. 测量周期长度

    1. a = 上面最后一个 x,b = 上面最后一个 x,计数器 c = 0
    2. a = 位置 a 的元素,b = 位置 b 的元素,b = 位置 b 的元素,c++(因此我们在循环中使用 b 前进 2 步,使用 a 在循环中前进 1 步)
    3. 如果 a == b 则循环长度为 c,否则继续执行步骤 2。
  3. 找到循环的入口点

    1. x = 最后一个元素
    2. x = 位置 x 的元素
    3. 重复步骤 2.c 次(总共)
    4. y = 最后一个元素
    5. 如果 x == y 那么 x 是一个解(x 完成了一个完整的循环,而 y 即将进入循环)
    6. x = 位置 x 的元素,y = 位置 y 的元素
    7. 重复第 5 步和第 6 步,直到找到解决方案。

3 个主要步骤都是 O(n) 和顺序的,因此整体复杂度也是 O(n),空间复杂度是 O(1)。

上面的例子:

  1. x 采用以下值:8 7 6 4 1 2 3 4 1 2

  2. a 采用以下值:2 3 4 1 2
    b 采用以下值:2 4 2 4 2
    因此 c = 4(是的,有 5 个数字,但 c 仅在进行步骤时增加,而不是最初增加)

  3. x 采用以下值:8 7 6 4 | 1 2 3 4
    y 采用以下值: | 8 7 6 4
    最后x == y == 4,这是一个解决方案!

cmets 中要求的示例 2:3 1 4 6 1 2 5

  1. 进入周期:5 1 3 4 6 2 1 3

  2. 测量周期长度:
    答:3 4 6 2 1 3
    b: 3 6 1 4 2 3
    c = 5

  3. 找到入口点:
    x: 5 1 3 4 6 | 2 1
    是:| 5 1
    x == y == 1 是一个解决方案

【讨论】:

  • 哇,真快!谢谢,我在这里的第一个赞成票 :) 面试官提到(我忘了补充)输入数组不应该被修改。在这种情况下你能想出一个解决方案吗?
  • @RoseM 仅在只有一个重复项时。
  • 这是复杂的部分 - 可能不止一个:/
  • 感谢更新的解决方案,试图掌握它。那么,如果我们处理元素 1、2、4、7,它们的异或将为 0,但在这个序列中没有重复?我是不是误会了什么?
  • @RoseM 1,2,4,7 不是区间 [1, n] 中的 n+1 个整数
【解决方案2】:

如果允许您对输入向量进行非破坏性修改,那么这很容易。假设我们可以通过取反来“标记”输入中的一个元素(这显然是可逆的)。在这种情况下,我们可以进行如下操作:

注意:以下假设向量从 1 开始索引。由于它可能从 0 开始索引(在大多数语言中),您可以通过“否定索引处的项目”来实现“标记索引 i 处的项目”索引 i-1"。

  1. 将 i 设置为 0 并执行以下循环:
    1. 递增 i 直到取消标记第 i 项。
    2. 将 j 设置为 i 并执行以下循环:
      1. 将 j 设置为向量[j]。
      2. 如果 j 处的项目被标记,则 j 是重复的。终止两个循环。
      3. 在 j 处标记项目。
      4. 如果 j != i,继续内部循环。
  2. 遍历向量,将每个元素设置为其绝对值(即取消标记所有内容以恢复向量)。

【讨论】:

  • 感谢您的回答,我 +1。面试官说“只读输入数组”,所以我认为即使这样也是不允许的。不过算法不错。
【解决方案3】:
  • 这取决于您(您的应用程序)可以使用哪些工具。目前存在很多框架/库。例如,在 C++ 标准的情况下,您可以使用 std::map ,正如 maraca 所提到的。

  • 或者,如果您有时间,您可以自己实现二叉树,但您需要记住,元素的插入与通常的数组不同。在这种情况下,您可以在特定情况下尽可能优化重复搜索。

二叉树解释。参考: https://www.wikiwand.com/en/Binary_tree

【讨论】:

  • 我可以使用地图,但不会是O(1) 空间。面试官特别要求固定空间:/
【解决方案4】:

这是一个可能的实现:

function checkDuplicate(arr) {
  console.log(arr.join(", "));
  let  len = arr.length
      ,pos = 0
      ,done = 0
      ,cur = arr[0]
      ;
  while (done < len) {
    if (pos === cur) {
      cur = arr[++pos];
    } else {
      pos = cur;
      if (arr[pos] === cur) {
        console.log(`> duplicate is ${cur}`);
        return cur;
      }
      cur = arr[pos];
    }
    done++;
  }
  console.log("> no duplicate");
  return -1;
}

for (t of [
     [0, 1, 2, 3]
    ,[0, 1, 2, 1]
    ,[1, 0, 2, 3]
    ,[1, 1, 0, 2, 4]
  ]) checkDuplicate(t);

这基本上是@maraca 提出的解决方案(输入太慢!)它具有恒定的空间要求(对于局部变量),但除此之外仅使用原始数组进行存储。在最坏的情况下应该是O(n),因为一旦发现重复,进程就会终止。

【讨论】:

  • 谢谢!面试官提到(我忘了补充)输入数组不应该被修改。在这种情况下你能想出一个解决方案吗?我已编辑我的问题以添加此要求。
  • [1, 2, 1, 0] 失败。
  • @giusti 0 不允许
  • 不在原始语句中,但答案将域映射到 [0, n-1]。无论如何,[1, 2, 1, 2] 也失败了。
猜你喜欢
  • 2016-11-18
  • 2018-05-16
  • 2018-09-05
  • 2021-12-10
相关资源
最近更新 更多