【问题标题】:Find all differences in an array in O(n)在 O(n) 中查找数组中的所有差异
【发布时间】:2012-01-17 06:45:30
【问题描述】:

问题:给定一个排序数组 A 找出所有可能的元素与 A 的差异。

我的解决方案:

for (int i=0; i<n-1; ++i) {
  for (int j=i+1; j<n; ++j) {
    System.out.println(Math.abs(ai-aj));
  }
}

当然,它是 O(n^2),但我一点也不多算。我在网上查了一下,发现了这个:http://www.careercup.com/question?id=9111881。它说你不能做得更好,但在一次采访中我被告知你可以做到 O(n)。哪个是对的?

【问题讨论】:

  • 我对在这家公司工作会很谨慎...他们可能希望您在 P 时间内解决 NP 完全问题... ;-)
  • 我的猜测是你或面试官忽略或无意中听到了一个额外的条件。
  • 将 n 定义为差异的数量(输出的大小而不是输入的大小)。嘿 presto - 现在是 O(n)。
  • 对于 sorted 数组,如果前一个元素等于当前 == 重复,则查找重复元素很简单。也许这个问题没有正确提出?
  • @bestsss 这个问题与寻找重复无关。

标签: java c arrays algorithm


【解决方案1】:

首先想到的是您没有使用数组已排序的事实。让我们假设它是递增的(递减可以类似地处理)。

我们也可以利用望远镜的差异(i>j):

a_i - a_j = (a_i - a_(i-1)) + (a_(i-1) - a_(i-2)) + ... + (a_(j+1) - a_j)

现在构建一个新序列,将其命名为 s,其区别很简单,意思是 (a_i - a_(i-1))。这只需要通过 (O(n)) 完成一次,您也可以跳过重复,这意味着如果 a_i = a_(i+1) 则跳过 a_i

a_i-a_ji&gt;j 的所有可能差异均采用 s_i + s_(i+1) + ... + s_(j+1) 的形式。因此,也许如果您将其视为找到它们,那么您在O(n) 时间就做到了。然而,要打印它们,可能需要多达n(n-1)/2 调用,而这绝对是O(n^2)

【讨论】:

  • 好的。我看到这如何使用数组已排序的事实,因为您不必担心绝对值。我仍然不认为这真的是 O(n),但这可能是他的想法。谢谢
【解决方案2】:

例如对于具有元素的数组 {21, 22, ..., 2n} 有 n⋅(n-1)/2 个可能的差异,而且没有两个是相等的。所以有O(n2)个差异。

由于您必须枚举所有这些,因此您还需要至少 O(n2) 时间。

【讨论】:

  • 这就是我给出的链接中的答案所说的,但面试官非常坚持认为有一个 O(n) 解决方案。
  • 由于a[last] - a[first] 是最大可能的差异,它也是(小于)最大可能的差异值数量。所以这是线性的,只是不是原始数组的大小。也许有一种方法可以检查每个差异是否实际上在时间上与潜在差异的数量呈线性关系,但我想不出它是 ATM。
  • 是的,O(n^2) 是最坏的情况,但请注意,某些差异可能会出现多次。出现了一个问题:是否有一个输出敏感算法,具有 O(k) 时间复杂度(或类似),其中 k 是唯一差异的数量?这意味着,例如,对于 k=O(n) 的特殊输入,算法只需 O(n)。
  • 例如,对于 [i, i, ..., i] 形式的输入,其中 i!=0,正好有 n-1 个差异。
【解决方案3】:

排序或未排序无关紧要,如果您必须计算每个差异,则无法在小于 n^2 的时间内完成,

问题被问错了,或者你只是做 O(n) 然后再打印 42 次其他 N 次:D

【讨论】:

    【解决方案4】:

    您可以通过在排序之前假设数组内容是随机整数来获得另一个反例。那么 Ai - Aj vs Ak - Al,甚至 Ai - Aj vs Aj - Ak 这两个差异相同的可能性太小,以至于只有 O(n) 个不同的差异 Ai - Aj。

    鉴于此,面试官的问题是解释允许 O(n) 解决方案的特殊情况。一种可能性是数组值都是 0..n 范围内的数字,因为在这种情况下,最大绝对差仅为 n。

    我可以在 O(n lg n) 中做到这一点,但不是 O(n)。用大小为 n+1 的数组表示数组内容,其中元素 i 设置为 1,其中数组中有一个值 i。然后使用 FFT 将数组与自身进行卷积 - 存在差异 Ai - Aj = k,其中卷积的第 k 个元素不为零。

    【讨论】:

      【解决方案5】:

      如果面试官喜欢理论游戏,也许他正在考虑使用输入和结果表? 任何输入大小有限制的问题,并且有一个已知的解决方案,都可以通过查表来解决。假设您首先创建并存储了该表,该表可能很大

      所以如果数组大小是有限的,这个问题可以通过查表来解决,这(给定一些假设)甚至可以在恒定时间内完成。当然,即使最大数组大小为 two(假设为 32 位整数),该表也不适合普通计算机的内存或磁盘。对于更大的数组最大尺寸,您将进入“不适合已知宇宙”的尺寸。但是,理论上是可以做到的。

      (但实际上,我认为 Jens Gustedt 的评论更有可能。)

      【讨论】:

        【解决方案6】:

        是的,您当然可以做到,但这是一个有点棘手的方法。 要找到 O(n) 中的差异,您需要使用 BitSet(C++) 或相应语言中的任何类似数据结构。

        初始化两个位集 A 和 B 您可以执行以下操作: 对于通过数组的每次迭代: 1--在BitSet A中存储连续的差异 2--左移 B 3--在BitSet B中存储连续差异 4--取A=A或B

        例如,我给出了代码- 这里 N 是数组的大小

        for (int i=1;i<N;i++){
            int diff = arr[i]-arr[i-1];
            A[diff]=1;
            B<<=diff;
            B[diff]=1;
            A=A | B;
        }
        

        A 中为 1 的位将是差异。

        【讨论】:

          【解决方案7】:

          首先需要对数组进行排序

          让我们考虑一个排序数组 ar = {1,2,3,4}

          所以我们在 O(n^2) 上做了什么

          for(int i=0; i<n; i++)
          for(int j=i+1; j<n; j++) sum+=abs(ar[i]-ar[j]);
          

          如果我们把这里的操作精细化,那么它会如下所示

          when i = 0 | sum = sum + {(2-1)+(3-1)+(4-1)}                   
          when i = 1 | sum = sum + {(3-2)+(4-2)}               
          when i = 2 | sum = sum + {(4-3)}
          

          如果我们把它们都写出来

          sum = ( -1-1-1) + (2+ -2-2) + (3+3 -3) + (4+4+4 ) 
          

          我们可以看到

          索引 0 处的数字加到总和中 0 次,并从总和中减去 3 次。
          索引 1 处的数字被加到总和中 1 次,并从总和中减去 2 次。
          索引 2 处的数字被加到总和中 2 次,并从总和中减去 1 次。
          索引 3 处的数字加到总和中 3 次,并从总和中减去 0 次。

          所以我们可以这么说,

          the number at index i will be added to the sum for i time 
          and will be substracted from the sum for (n-i)-1 time
          

          然后是广义表达式 每个元素将是

          sum = sum + (i*a[i]) – ((n-i)-1)*a[i];
          

          【讨论】:

            猜你喜欢
            • 2021-01-21
            • 1970-01-01
            • 2018-07-28
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多