简单的面试问题变得更难:给定数字1..100,找到缺少的正确缺少的数字

时间:2010-08-16 10:26:58

标签: algorithm math

我有一段时间有一次有趣的求职面试经历。问题开始很简单:

  

Q1 :我们有一个包含数字123,...,100的包。每个数字只出现一次,因此有100个数字。现在从包里随机挑出一个号码。找到丢失的号码。

我之前听过这个采访问题,所以我很快就回答了这个问题:

  

A1 :好吧,数字1 + 2 + 3 + … + N的总和是(N+1)(N/2)(参见Wikipedia: sum of arithmetic series)。对于N = 100,总和为5050

     

因此,如果包中存在所有数字,则总和将是5050。由于缺少一个数字,总和将小于此数值,差异就是该数字。因此,我们可以在O(N)时间和O(1)空格中找到丢失的数字。

此时我觉得我做得很好,但突然之间问题发生了意想不到的变化:

  

Q2 :这是正确的,但现在如果 TWO 号码丢失,你会怎么做呢?

我之前从未见过/听过/考虑过这种变化,所以我惊慌失措,无法回答这个问题。面试官坚持要知道我的思考过程,所以我提到也许我们可以通过与预期产品进行比较来获得更多信息,或者可能在从第一遍获得一些信息后再做第二遍,但我真的只是拍摄在黑暗中而不是实际上有一条清晰的解决方案。

面试官确实试图鼓励我说有第二个等式确实是解决问题的一种方法。在这一点上,我有点不高兴(因为事先不知道答案),并询问这是一般的(阅读:“有用”)编程技术,还是只是一个技巧/问题答案。

面试官的回答让我感到惊讶:你可以概括一下找到3个缺失数字的技巧。实际上,您可以将其概括为找到 k 缺失的数字。

  

Qk :如果行李中缺少 k 号码,您会如何有效地找到它?

几个月前,我仍然无法弄清楚这种技术是什么。显然有Ω(N)时间下限,因为我们必须扫描所有数字至少一次,但是访问者坚持解决技术的 TIME SPACE 复杂性(减去O(N)时间输入扫描)在 k 中定义,而不是 N

所以这里的问题很简单:

  • 您如何解决 Q2
  • 您如何解决 Q3
  • 您如何解决 Qk

澄清

  • 一般来说,1 <。 N 中有 N 个数字,而不仅仅是1..100。
  • 我不是在寻找明显的基于集合的解决方案,例如:使用bit set,使用指定位的值对每个数字的存在/不存在进行编码,因此在额外空间中使用O(N)位。我们无法承受与 N 成比例的任何额外空间。
  • 我也不是在寻找明显的排序优先方法。这个和基于集合的方法在一次采访中值得一提(它们易于实现,并且取决于 N ,可能非常实用)。我正在寻找圣杯解决方案(可能实现也可能不实用,但仍具有所需的渐近特征)。

所以再次,当然你必须扫描O(N)中的输入,但是你只能捕获少量的信息(根据 k 而不是 N ),然后必须以某种方式找到 k 缺失的数字。

49 个答案:

答案 0 :(得分:565)

以下是Dimitris Andreou's link的摘要。

记住i次幂的总和,其中i = 1,2,...,k。这减少了求解方程组的问题

a 1 + a 2 + ... + a k = b 1

a 1 2 + a 2 2 + ... + a k 2 = b 2

...

a 1 k + a 2 k + ... + a k k = b k

使用Newton's identities,知道b i 允许计算

c 1 = a 1 + a 2 + ... a k

c 2 = a 1 a 2 + a 1 a 3 + ... + a k-1 a k

...

c k = a 1 a 2 ... a k

如果展开多项式(xa 1 )...(xa k ),系数将精确地为c 1 ,... 。,c k - 见Viète's formulas。由于每个多项式因子唯一(多项式的环是Euclidean domain),这意味着 i 是唯一确定的,直到排列。

这结束了证明记住权力足以恢复数字的证据。对于常数k,这是一个很好的方法。

然而,当k变化时,计算c 1 ,...,c k 的直接方法是非常昂贵的,因为例如c k 是所有缺失数字的乘积,幅度为n!/(n-k)!.为了克服这一点,执行computations in Zq field,其中q是素数,使得n <= q&lt; 2n - 它由Bertrand's postulate存在。证明不需要改变,因为公式仍然成立,并且多项式的因子分解仍然是唯一的。您还需要一种算法来对有限域进行分解,例如BerlekampCantor-Zassenhaus的算法。

常数k的高级伪代码:

  • 计算给定数字的第i个幂
  • 减去获得未知数的第i个幂的总和。调用总和b i
  • 使用牛顿的身份来计算b i 的系数;称他们为c i 。基本上,c 1 = b 1 ; c 2 =(c 1 b 1 - b 2 )/ 2;请参阅Wikipedia了解确切的公式
  • 因子多项式x k -c 1 x k-1 + ... + c k
  • 多项式的根是所需的数字a 1 ,...,a k

对于变化的k,找到素数n&lt; = q&lt; 2n使用例如米勒 - 拉宾,并执行所有数字减少模数q的步骤。

编辑:此答案的先前版本指出,代替Z q ,其中q为素数,可以使用特征2的有限域(q = 2 ^(log n) )。事实并非如此,因为牛顿公式要求按数字除以k。

答案 1 :(得分:235)

您可以通过阅读 Muthukrishnan - Data Stream Algorithms: Puzzle 1: Finding Missing Numbers 的几页来找到它。 它显示了您正在寻找的概括。这可能是你的面试官所读的以及他提出这些问题的原因。

现在,如果只有人们会开始删除被Muthukrishnan治疗所包含或取代的答案,并使这个文本更容易找到。 :)


另请参阅sdcvvc's直接相关的答案,其中还包括伪代码(欢呼!无需阅读那些棘手的数学公式:))(谢谢,干得好!)。

答案 2 :(得分:169)

我们可以通过将数字本身和数字的正方形相加来解决Q2。

然后我们可以将问题减少到

k1 + k2 = x
k1^2 + k2^2 = y

其中xy的总和低于预期值。

替代给了我们:

(x-k2)^2 + k2^2 = y

然后我们可以解决以确定我们缺少的数字。

答案 3 :(得分:130)

正如@j_random_hacker指出的那样,这与Finding duplicates in O(n) time and O(1) space非常相似,我的答案也适用于此。

假设“bag”由大小为A[]的基于1的数组N - k表示,我们可以在O(N)时间和O(k)额外空间中解决Qk。

首先,我们通过A[]元素扩展数组k,使其现在的大小为N。这是O(k)额外空间。然后我们运行以下伪代码算法:

for i := n - k + 1 to n
    A[i] := A[1]
end for

for i := 1 to n - k
    while A[A[i]] != A[i] 
        swap(A[i], A[A[i]])
    end while
end for

for i := 1 to n
    if A[i] != i then 
        print i
    end if
end for

第一个循环将k个额外条目初始化为与数组中的第一个条目相同(这只是我们知道已经存在于数组中的一个方便的值 - 在此步骤之后,任何条目都是在扩展数组中仍然缺少大小为N-k的初始数组中的缺失。)

第二个循环置换扩展数组,以便如果元素x至少存在一次,那么其中一个条目将位于A[x]位置。

请注意,虽然它有一个嵌套循环,但它仍然在O(N)时间内运行 - 只有i这样A[i] != i时才会发生交换,并且每个交换至少设置一次一个元素,如A[i] == i,之前不是真的。这意味着交换总数(以及while循环体的总执行次数)最多为N-1

第三个循环打印未被值i占用的数组i的索引 - 这意味着i必定已丢失。

答案 4 :(得分:119)

我问一个4岁的孩子来解决这个问题。他对数字进行了排序,然后计算在内。这有一个空间要求O(厨房地板),它的工作原理同样容易,但很多球丢失。

答案 5 :(得分:34)

不确定,如果它是最有效的解决方案,但我会遍历所有条目,并使用bitset记住,设置哪些数字,然后测试0位。

我喜欢简单的解决方案 - 我甚至相信,它可能比计算总和或平方和等更快。

答案 6 :(得分:31)

我没有检查数学,但我怀疑在计算Σ(n^2)的同一遍中计算Σ(n)会提供足够的信息来获取两个缺失的数字,Do Σ(n^3) as好吧,如果有三个,依此类推。

答案 7 :(得分:15)

基于数字总和的解决方案的问题是它们没有考虑存储和使用具有大指数的数字的成本......在实践中,它适用于非常大的n,一个大数字库会被使用。我们可以分析这些算法的空间利用率。

我们可以分析sdcvvc和Dimitris Andreou算法的时间和空间复杂性。

存储

l_j = ceil (log_2 (sum_{i=1}^n i^j))
l_j > log_2 n^j  (assuming n >= 0, k >= 0)
l_j > j log_2 n \in \Omega(j log n)

l_j < log_2 ((sum_{i=1}^n i)^j) + 1
l_j < j log_2 (n) + j log_2 (n + 1) - j log_2 (2) + 1
l_j < j log_2 n + j + c \in O(j log n)`

所以l_j \in \Theta(j log n)

使用的总存储空间:\sum_{j=1}^k l_j \in \Theta(k^2 log n)

使用的空间:假设计算a^j花费ceil(log_2 j)时间,总时间:

t = k ceil(\sum_i=1^n log_2 (i)) = k ceil(log_2 (\prod_i=1^n (i)))
t > k log_2 (n^n + O(n^(n-1)))
t > k log_2 (n^n) = kn log_2 (n)  \in \Omega(kn log n)
t < k log_2 (\prod_i=1^n i^i) + 1
t < kn log_2 (n) + 1 \in O(kn log n)

使用的总时间:\Theta(kn log n)

如果这个时间和空间令人满意,你可以使用简单的递归 算法。设b!我是包中的第i个条目,n之前的数字 删除,k删除的数量。在Haskell语法中......

let
  -- O(1)
  isInRange low high v = (v >= low) && (v <= high)
  -- O(n - k)
  countInRange low high = sum $ map (fromEnum . isInRange low high . (!)b) [1..(n-k)]
  findMissing l low high krange
    -- O(1) if there is nothing to find.
    | krange=0 = l
    -- O(1) if there is only one possibility.
    | low=high = low:l
    -- Otherwise total of O(knlog(n)) time
    | otherwise =
       let
         mid = (low + high) `div` 2
         klow = countInRange low mid
         khigh = krange - klow
       in
         findMissing (findMissing low mid klow) (mid + 1) high khigh
in
  findMising 1 (n - k) k

使用的存储空间:O(k)表示列表,O(log(n))表示堆栈:O(k + log(n)) 该算法更直观,时间复杂度相同,占用空间更小。

答案 8 :(得分:12)

等一下。正如问题所述,包里有100个号码。无论k有多大,问题都可以在恒定时间内解决,因为你可以在一个循环的最多100-k次迭代中使用一个集合并从集合中删除数字。 100是不变的。剩下的数字是你的答案。

如果我们将解决方案推广到从1到N的数字,除了N不是常数之外什么都没有变化,所以我们处于O(N-k)= O(N)时间。例如,如果我们使用位集,我们在O(N)时间内将位设置为1,迭代数字,将位设置为0(O(Nk)= O(N))然后我们有答案。

在我看来,面试官问你如何在O(k)时间而不是O(N)时间打印最终集的内容。显然,通过位设置,您必须遍历所有N位以确定是否应该打印该数字。但是,如果更改实现集的方式,则可以以k次迭代打印出数字。这是通过将数字放入要存储在哈希集和双向链表中的对象来完成的。从哈希集中删除对象时,也会从列表中删除它。答案将保留在列表中,该列表现在的长度为k。

答案 9 :(得分:7)

要解决2(和3)个缺失数字问题,您可以修改quickselectDemo平均在O(n)中运行,如果分区就地使用,则使用常量内存。

  1. 将关于随机数据透视p的集合划分为包含小于数据透视表的数字的分区lr,其中包含大于数据透视表的数字

  2. 通过将数据透视值与每个分区的大小(p - 1 - count(l) = count of missing numbers in l进行比较来确定2个缺失数字所在的分区 n - count(r) - p = count of missing numbers in r

  3. a)如果每个分区缺少一个数字,则使用总和方法来查找每个缺失的数字。

    (1 + 2 + ... + (p-1)) - sum(l) = missing #1和   ((p+1) + (p+2) ... + n) - sum(r) = missing #2

    b)如果一个分区缺少两个数字且分区为空,则缺少的数字为(p-1,p-2)(p+1,p+2)   取决于哪个分区缺少数字。

    如果一个分区缺少2个数字但不为空,则递归到该分区。

  4. 只有2个缺失的数字,此算法始终丢弃至少一个分区,因此它保留了O(n)快速选择的平均时间复杂度。类似地,如果缺少3个数字,则该算法还会在每次传递时丢弃至少一个分区(因为与2个缺失的数字一样,最多只有1个分区将包含多个缺失的数字)。但是,当添加更多缺失数字时,我不确定性能会下降多少。

    这是使用就地分区的实现,因此这个示例不满足空间要求,但它确实说明了算法的步骤:

    <?php
    
      $list = range(1,100);
      unset($list[3]);
      unset($list[31]);
    
      findMissing($list,1,100);
    
      function findMissing($list, $min, $max) {
        if(empty($list)) {
          print_r(range($min, $max));
          return;
        }
    
        $l = $r = [];
        $pivot = array_pop($list);
    
        foreach($list as $number) {
          if($number < $pivot) {
            $l[] = $number;
          }
          else {
            $r[] = $number;
          }
        }
    
        if(count($l) == $pivot - $min - 1) {
          // only 1 missing number use difference of sums
          print array_sum(range($min, $pivot-1)) - array_sum($l) . "\n";
        }
        else if(count($l) < $pivot - $min) {
          // more than 1 missing number, recurse
          findMissing($l, $min, $pivot-1);
        }
    
        if(count($r) == $max - $pivot - 1) {
          // only 1 missing number use difference of sums
          print array_sum(range($pivot + 1, $max)) - array_sum($r) . "\n";
        } else if(count($r) < $max - $pivot) {
          // mroe than 1 missing number recurse
          findMissing($r, $pivot+1, $max);
        }
      }
    

    {{3}}

答案 10 :(得分:7)

这是一个使用k位额外存储的解决方案,没有任何巧妙的技巧,只是直截了当。执行时间O(n),额外空间O(k)。只是为了证明这可以在不首先阅读解决方案或成为天才的情况下解决:

void puzzle (int* data, int n, bool* extra, int k)
{
    // data contains n distinct numbers from 1 to n + k, extra provides
    // space for k extra bits. 

    // Rearrange the array so there are (even) even numbers at the start
    // and (odd) odd numbers at the end.
    int even = 0, odd = 0;
    while (even + odd < n)
    {
        if (data [even] % 2 == 0) ++even;
        else if (data [n - 1 - odd] % 2 == 1) ++odd;
        else { int tmp = data [even]; data [even] = data [n - 1 - odd]; 
               data [n - 1 - odd] = tmp; ++even; ++odd; }
    }

    // Erase the lowest bits of all numbers and set the extra bits to 0.
    for (int i = even; i < n; ++i) data [i] -= 1;
    for (int i = 0; i < k; ++i) extra [i] = false;

    // Set a bit for every number that is present
    for (int i = 0; i < n; ++i)
    {
        int tmp = data [i];
        tmp -= (tmp % 2);
        if (i >= even) ++tmp;
        if (tmp <= n) data [tmp - 1] += 1; else extra [tmp - n - 1] = true;
    }

    // Print out the missing ones
    for (int i = 1; i <= n; ++i)
        if (data [i - 1] % 2 == 0) printf ("Number %d is missing\n", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing\n", i);

    // Restore the lowest bits again.
    for (int i = 0; i < n; ++i) {
        if (i < even) { if (data [i] % 2 != 0) data [i] -= 1; }
        else { if (data [i] % 2 == 0) data [i] += 1; }
    }
}

答案 11 :(得分:7)

第二季度的一个非常简单的解决方案,我很惊讶没有人回答。使用Q1中的方法来找到两个遗漏数字的总和。让我们用S来表示它,那么丢失的数字之一小于S / 2,另一个大于S / 2(duh)。将所有从1到S / 2的数字求和,然后将其与公式的结果进行比较(类似于Q1中的方法),以找到缺失数字之间的较低者。从S减去它以找到更大的遗漏号。

答案 12 :(得分:5)

您能检查每个号码是否存在吗?如果是,您可以试试这个:

  

S =袋中所有数字的总和(S <5050)
  Z =缺失数字的总和5050-S

如果缺少的数字为xy则为:

  

x = Z - y和
  max(x)= Z - 1

因此,请检查从1max(x)的范围并找到号码

答案 13 :(得分:4)

可能这个算法适用于问题1:

  1. 预先计算前100个整数的xor(val = 1 ^ 2 ^ 3 ^ 4 .... 100)
  2. xor元素,因为它们不断来自输入流(val1 = val1 ^ next_input)
  3. 最终答案= val ^ val1
  4. 甚至更好:

    def GetValue(A)
      val=0
      for i=1 to 100
        do
          val=val^i
        done
      for value in A:
        do
          val=val^value 
        done
      return val
    

    实际上,该算法可以扩展为两个缺失的数字。第一步保持不变。当我们使用两个缺少的数字调用GetValue时,结果将是a1^a2两个缺少的数字。让我们说

    val = a1^a2

    现在从val中筛出a1和a2,我们取val中的任何设定位。假设ith位在val中设置。这意味着a1和a2在ith位位置具有不同的奇偶校验。 现在我们在原始数组上进行另一次迭代并保留两个xor值。一个用于具有第i位设置的数字,另一个用于没有第i位设置的数字。我们现在有两个数字桶,它保证a1 and a2将位于不同的桶中。现在重复我们在每个桶上找到一个缺少元素所做的相同操作。

答案 14 :(得分:3)

对于Q2,这是一个比其他解决方案效率低一点的解决方案,但仍然具有O(N)运行时并占用O(k)空间。

这个想法是运行原始算法两次。在第一个中,您会得到一个缺失的总数,它会为您提供缺失数字的上限。我们将此号码称为N。您知道丢失的两个数字将总计为N,因此第一个数字只能位于[1, floor((N-1)/2)]区间,而第二个数字将位于[floor(N/2)+1,N-1]

因此,您再次循环所有数字,丢弃第一个间隔中未包含的所有数字。那些是,你跟踪他们的总和。最后,您将知道其中一个缺失的两个数字,并通过扩展知道第二个数字。

我觉得这种方法可以推广,也许可以在&#34; parallel&#34;在一次通过输入的过程中,但我还没弄明白如何。

答案 15 :(得分:3)

如果你有两个列表的总和和两个列表的乘积,你可以解决Q2。

(l1是原始的,l2是修改后的列表)

d = sum(l1) - sum(l2)
m = mul(l1) / mul(l2)

我们可以对此进行优化,因为算术系列的总和是第一个和最后一个项的平均值的n倍:

n = len(l1)
d = (n/2)*(n+1) - sum(l2)

现在我们知道(如果a和b是删除的数字):

a + b = d
a * b = m

所以我们可以重新安排到:

a = s - b
b * (s - b) = m

然后成倍增加:

-b^2 + s*b = m

然后重新排列,使右侧为零:

-b^2 + s*b - m = 0

然后我们可以用二次公式求解:

b = (-s + sqrt(s^2 - (4*-1*-m)))/-2
a = s - b

示例Python 3代码:

from functools import reduce
import operator
import math
x = list(range(1,21))
sx = (len(x)/2)*(len(x)+1)
x.remove(15)
x.remove(5)
mul = lambda l: reduce(operator.mul,l)
s = sx - sum(x)
m = mul(range(1,21)) / mul(x)
b = (-s + math.sqrt(s**2 - (-4*(-m))))/-2
a = s - b
print(a,b) #15,5

我不知道sqrt,reduce和sum函数的复杂性,所以我无法弄清楚这个解决方案的复杂性(如果有人知道请在下面评论。)

答案 16 :(得分:2)

这是一个不像sdcvvc / Dimitris Andreou的答案那样依赖复杂数学的解决方案,不像caf和Panic Panic那样改变输入数组,并且不像Chris Lercher那样使用巨大的位集,JeremyP等许多人做到了。基本上,我从Svalorzen / Gilad Deutch的Q2想法开始,将其推广到普通情况Qk并用Java实现以证明该算法有效。

想法

假设我们有一个任意间隔 I ,我们仅知道它包含至少一个缺失的数字。在通过输入数组一次之后,仅查看 I 中的数字,我们就可以得到总和 S 和数量 Q I 中的数字。我们通过在每次遇到 I 中的一个数时简单地减小 I's 的长度(以获得Qem)来减少Im的长度,并通过减少每次 I 中所有遇到的数字(用于获取 S )。

现在我们来看 S Q 。如果 Q = 1 ,则表示 I 仅包含缺失的数字之一,而该数字显然是 S 。我们将 I 标记为已完成(在程序中称为“明确”),并将其排除在外。另一方面,如果 Q> 1 ,我们可以计算 I 中包含的缺失数字的平均 A = S / Q 。由于所有数字都是不同的,因此此类数字中的至少一个严格小于 A ,并且至少一个严格大于 A 。现在我们将 A 中的 I 分成两个较小的间隔,每个间隔至少包含一个缺失数。请注意,如果它是整数,则分配给 A 的间隔并不重要。

我们进行下一个数组遍历,分别为每个间隔(但在同一遍中)计算 S Q ,然后用 Q标记间隔= 1 ,并用 Q> 1 分割间隔。我们继续进行此过程,直到没有新的“模棱两可”的间隔,即我们没有要分割的内容,因为每个间隔都恰好包含一个缺失的数字(并且我们总是知道这个数字,因为我们知道 S )。我们从包含所有可能数字的唯一“整个范围”间隔开始(例如问题中的 [1..N] )。

时间和空间复杂度分析

在流程停止之前,我们需要通过的总数 p 永远不会大于丢失的计数 k 。不等式 p <= k 可以得到严格证明。另一方面,还存在一个经验上限 p 2 N + 3 ,对于大值 k 有用。我们需要对输入数组的每个数字进行二进制搜索,以确定它所属的间隔。这会将 log k 乘数添加到时间复杂度上。

时间复杂度总计为 O(N᛫min(k,log N)᛫log k) 。请注意,对于大的 k ,这明显优于sdcvvc / Dimitris Andreou的方法,即 O(N N k)

对于其工作,该算法需要 O(k) 额外的空间来存储最多 k 个间隔,这比< “位集”解决方案中的em> O(N)

Java实现

这是一个实现上述算法的Java类。它总是返回排序的缺失数字数组。除此之外,它不需要遗漏的数字 k ,因为它是在第一遍计算的。整个数字范围由minNumbermaxNumber参数给出(例如,问题中的第一个示例为1和100)。

public class MissingNumbers {
    private static class Interval {
        boolean ambiguous = true;
        final int begin;
        int quantity;
        long sum;

        Interval(int begin, int end) { // begin inclusive, end exclusive
            this.begin = begin;
            quantity = end - begin;
            sum = quantity * ((long)end - 1 + begin) / 2;
        }

        void exclude(int x) {
            quantity--;
            sum -= x;
        }
    }

    public static int[] find(int minNumber, int maxNumber, NumberBag inputBag) {
        Interval full = new Interval(minNumber, ++maxNumber);
        for (inputBag.startOver(); inputBag.hasNext();)
            full.exclude(inputBag.next());
        int missingCount = full.quantity;
        if (missingCount == 0)
            return new int[0];
        Interval[] intervals = new Interval[missingCount];
        intervals[0] = full;
        int[] dividers = new int[missingCount];
        dividers[0] = minNumber;
        int intervalCount = 1;
        while (true) {
            int oldCount = intervalCount;
            for (int i = 0; i < oldCount; i++) {
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    if (itv.quantity == 1) // number inside itv uniquely identified
                        itv.ambiguous = false;
                    else
                        intervalCount++; // itv will be split into two intervals
            }
            if (oldCount == intervalCount)
                break;
            int newIndex = intervalCount - 1;
            int end = maxNumber;
            for (int oldIndex = oldCount - 1; oldIndex >= 0; oldIndex--) {
                // newIndex always >= oldIndex
                Interval itv = intervals[oldIndex];
                int begin = itv.begin;
                if (itv.ambiguous) {
                    // split interval itv
                    // use floorDiv instead of / because input numbers can be negative
                    int mean = (int)Math.floorDiv(itv.sum, itv.quantity) + 1;
                    intervals[newIndex--] = new Interval(mean, end);
                    intervals[newIndex--] = new Interval(begin, mean);
                } else
                    intervals[newIndex--] = itv;
                end = begin;
            }
            for (int i = 0; i < intervalCount; i++)
                dividers[i] = intervals[i].begin;
            for (inputBag.startOver(); inputBag.hasNext();) {
                int x = inputBag.next();
                // find the interval to which x belongs
                int i = java.util.Arrays.binarySearch(dividers, 0, intervalCount, x);
                if (i < 0)
                    i = -i - 2;
                Interval itv = intervals[i];
                if (itv.ambiguous)
                    itv.exclude(x);
            }
        }
        assert intervalCount == missingCount;
        for (int i = 0; i < intervalCount; i++)
            dividers[i] = (int)intervals[i].sum;
        return dividers;
    }
}

为了公平起见,此类接收NumberBag对象形式的输入。 NumberBag不允许进行数组修改和随机访问,并且还会计算请求数组进行顺序遍历的次数。与Iterable<Integer>相比,它还更适合用于大型数组测试,因为它避免了对原始int值进行装箱,并允许包装大的int[]的一部分以方便测试准备。如果需要,可以通过将两个for循环更改为foreach来用NumberBagint[]类型的Iterable<Integer>find类型替换import java.util.*; public abstract class NumberBag { private int passCount; public void startOver() { passCount++; } public final int getPassCount() { return passCount; } public abstract boolean hasNext(); public abstract int next(); // A lightweight version of Iterable<Integer> to avoid boxing of int public static NumberBag fromArray(int[] base, int fromIndex, int toIndex) { return new NumberBag() { int index = toIndex; public void startOver() { super.startOver(); index = fromIndex; } public boolean hasNext() { return index < toIndex; } public int next() { if (index >= toIndex) throw new NoSuchElementException(); return base[index++]; } }; } public static NumberBag fromArray(int[] base) { return fromArray(base, 0, base.length); } public static NumberBag fromIterable(Iterable<Integer> base) { return new NumberBag() { Iterator<Integer> it; public void startOver() { super.startOver(); it = base.iterator(); } public boolean hasNext() { return it.hasNext(); } public int next() { return it.next(); } }; } } 。 / p>

import java.util.*;

public class SimpleTest {
    public static void main(String[] args) {
        int[] input = { 7, 1, 4, 9, 6, 2 };
        NumberBag bag = NumberBag.fromArray(input);
        int[] output = MissingNumbers.find(1, 10, bag);
        System.out.format("Input: %s%nMissing numbers: %s%nPass count: %d%n",
                Arrays.toString(input), Arrays.toString(output), bag.getPassCount());

        List<Integer> inputList = new ArrayList<>();
        for (int i = 0; i < 10; i++)
            inputList.add(2 * i);
        Collections.shuffle(inputList);
        bag = NumberBag.fromIterable(inputList);
        output = MissingNumbers.find(0, 19, bag);
        System.out.format("%nInput: %s%nMissing numbers: %s%nPass count: %d%n",
                inputList, Arrays.toString(output), bag.getPassCount());

        // Sieve of Eratosthenes
        final int MAXN = 1_000;
        List<Integer> nonPrimes = new ArrayList<>();
        nonPrimes.add(1);
        int[] primes;
        int lastPrimeIndex = 0;
        while (true) {
            primes = MissingNumbers.find(1, MAXN, NumberBag.fromIterable(nonPrimes));
            int p = primes[lastPrimeIndex]; // guaranteed to be prime
            int q = p;
            for (int i = lastPrimeIndex++; i < primes.length; i++) {
                q = primes[i]; // not necessarily prime
                int pq = p * q;
                if (pq > MAXN)
                    break;
                nonPrimes.add(pq);
            }
            if (q == p)
                break;
        }
        System.out.format("%nSieve of Eratosthenes. %d primes up to %d found:%n",
                primes.length, MAXN);
        for (int i = 0; i < primes.length; i++)
            System.out.format(" %4d%s", primes[i], (i % 10) < 9 ? "" : "\n");
    }
}

测试

下面给出了演示这些类用法的简单示例。

import java.util.*;

public class BatchTest {
    private static final Random rand = new Random();
    public static int MIN_NUMBER = 1;
    private final int minNumber = MIN_NUMBER;
    private final int numberCount;
    private final int[] numbers;
    private int missingCount;
    public long finderTime;

    public BatchTest(int numberCount) {
        this.numberCount = numberCount;
        numbers = new int[numberCount];
        for (int i = 0; i < numberCount; i++)
            numbers[i] = minNumber + i;
    }

    private int passBound() {
        int mBound = missingCount > 0 ? missingCount : 1;
        int nBound = 34 - Integer.numberOfLeadingZeros(numberCount - 1); // ceil(log_2(numberCount)) + 2
        return Math.min(mBound, nBound);
    }

    private void error(String cause) {
        throw new RuntimeException("Error on '" + missingCount + " from " + numberCount + "' test, " + cause);
    }

    // returns the number of times the input array was traversed in this test
    public int makeTest(int missingCount) {
        this.missingCount = missingCount;
        // numbers array is reused when numberCount stays the same,
        // just Fisher–Yates shuffle it for each test
        for (int i = numberCount - 1; i > 0; i--) {
            int j = rand.nextInt(i + 1);
            if (i != j) {
                int t = numbers[i];
                numbers[i] = numbers[j];
                numbers[j] = t;
            }
        }
        final int bagSize = numberCount - missingCount;
        NumberBag inputBag = NumberBag.fromArray(numbers, 0, bagSize);
        finderTime -= System.nanoTime();
        int[] found = MissingNumbers.find(minNumber, minNumber + numberCount - 1, inputBag);
        finderTime += System.nanoTime();
        if (inputBag.getPassCount() > passBound())
            error("too many passes (" + inputBag.getPassCount() + " while only " + passBound() + " allowed)");
        if (found.length != missingCount)
            error("wrong result length");
        int j = bagSize; // "missing" part beginning in numbers
        Arrays.sort(numbers, bagSize, numberCount);
        for (int i = 0; i < missingCount; i++)
            if (found[i] != numbers[j++])
                error("wrong result array, " + i + "-th element differs");
        return inputBag.getPassCount();
    }

    public static void strideCheck(int numberCount, int minMissing, int maxMissing, int step, int repeats) {
        BatchTest t = new BatchTest(numberCount);
        System.out.println("╠═══════════════════════╬═════════════════╬═════════════════╣");
        for (int missingCount = minMissing; missingCount <= maxMissing; missingCount += step) {
            int minPass = Integer.MAX_VALUE;
            int passSum = 0;
            int maxPass = 0;
            t.finderTime = 0;
            for (int j = 1; j <= repeats; j++) {
                int pCount = t.makeTest(missingCount);
                if (pCount < minPass)
                    minPass = pCount;
                passSum += pCount;
                if (pCount > maxPass)
                    maxPass = pCount;
            }
            System.out.format("║ %9d  %9d  ║  %2d  %5.2f  %2d  ║  %11.3f    ║%n", missingCount, numberCount, minPass,
                    (double)passSum / repeats, maxPass, t.finderTime * 1e-6 / repeats);
        }
    }

    public static void main(String[] args) {
        System.out.println("╔═══════════════════════╦═════════════════╦═════════════════╗");
        System.out.println("║      Number count     ║      Passes     ║  Average time   ║");
        System.out.println("║   missimg     total   ║  min  avg   max ║ per search (ms) ║");
        long time = System.nanoTime();
        strideCheck(100, 0, 100, 1, 20_000);
        strideCheck(100_000, 2, 99_998, 1_282, 15);
        MIN_NUMBER = -2_000_000_000;
        strideCheck(300_000_000, 1, 10, 1, 1);
        time = System.nanoTime() - time;
        System.out.println("╚═══════════════════════╩═════════════════╩═════════════════╝");
        System.out.format("%nSuccess. Total time: %.2f s.%n", time * 1e-9);
    }
}

可以通过以下方式执行大型数组测试:

{{1}}

Try them out on Ideone

答案 17 :(得分:2)

我认为这可以在没有任何复杂的数学方程和理论的情况下完成。以下是现场和O(2n)时间复杂度解决方案的提案:

输入表格假设:

bag = n

中的数字#

缺少数字的数量= k

包中的数字由长度为n

的数组表示

algo = n

的输入数组长度

数组中缺少的条目(从包中取出的数字)将被数组中第一个元素的值替换。

EG。最初的包看起来像[2,9,3,7,8,6,4,5,1,10]。 如果取出4,则值4将变为2(数组的第一个元素)。 因此,在取出4个包后,看起来会像[2,9,3,7,8,6,2,5,1,10]

此解决方案的关键是通过在遍历数组时取消该INDEX处的值来标记访问数字的INDEX。

    IEnumerable<int> GetMissingNumbers(int[] arrayOfNumbers)
    {
        List<int> missingNumbers = new List<int>();
        int arrayLength = arrayOfNumbers.Length;

        //First Pass
        for (int i = 0; i < arrayLength; i++)
        {
            int index = Math.Abs(arrayOfNumbers[i]) - 1;
            if (index > -1)
            {
                arrayOfNumbers[index] = Math.Abs(arrayOfNumbers[index]) * -1; //Marking the visited indexes
            }
        }

        //Second Pass to get missing numbers
        for (int i = 0; i < arrayLength; i++)
        {                
            //If this index is unvisited, means this is a missing number
            if (arrayOfNumbers[i] > 0)
            {
                missingNumbers.Add(i + 1);
            }
        }

        return missingNumbers;
    }

答案 18 :(得分:2)

您可能需要澄清O(k)的含义。

这里是任意k的一个简单解决方案:对于你的数字集合中的每个v,累加2 ^ v的总和。最后,循环i从1到N.如果与2 ^ i按位和,则为零,则i丢失。 (或者数字上,如果总和除以2 ^ i的平均值是偶数。或sum modulo 2^(i+1)) < 2^i。)

简单,对吗? O(N)时间,O(1)存储,并且它支持任意k。

除非您计算在真实计算机上的大量数字,否则每个都需要O(N)空间。实际上,这个解决方案与位向量相同。

所以你可以聪明地计算平方和平方和和平方和的总和......直到v ^ k的总和,然后用花哨的数学来提取结果。但这些也是大数字,这引出了一个问题:我们在谈论什么样的抽象运作模式?在O(1)空间中适合多少,以及总结所需数量的数量需要多长时间?

答案 19 :(得分:2)

有一种通用的方法来推广像这样的流媒体算法。 我们的想法是使用一些随机化来有希望地将k元素“扩散”到独立的子问题中,我们的原始算法为我们解决了问题。除了其他方面,该技术还用于稀疏信号重建。

  • 制作一个大小为a的数组u = k^2
  • 选择任意universal hash functionh : {1,...,n} -> {1,...,u}。 (如multiply-shift
  • 对于i增加1, ..., n
  • 中的每个a[h(i)] += i
  • 对于输入流中的每个数字x,递减a[h(x)] -= x

如果所有缺失的数字都被散列到不同的桶中,则数组的非零元素现在将包含缺失的数字。

通过定义通用散列函数,将特定对发送到同一个桶的概率小于1/u。由于大约有k^2/2对,我们认为错误概率最多为k^2/2/u=1/2。也就是说,我们成功的概率至少为50%,如果我们增加u,我们就会增加机会。

请注意,此算法占用k^2 logn位空间(每个阵列桶需要logn位。)这与@Dimitris Andreou的答案所需的空间相匹配(特别是多项式因子分解的空间要求,这也恰好是随机的。) 此算法在每次更新时也有恒定的时间,而不是在幂和的情况下的时间k

事实上,通过使用评论中描述的技巧,我们可以比功率和方法更有效。

答案 20 :(得分:1)

我会对这个问题采取不同的方法,并向访调员探讨他正试图解决的更大问题的更多细节。根据问题及其周围的要求,明显的基于集合的解决方案可能是正确的,并且生成列表和后续选择方法可能不会。

例如,可能是面试官要发送n条消息,并且需要知道没有得到回复的k,并且需要以小挂钟的方式知道它n-k回复到达后的时间。我们还要说消息通道的本质是即使在全口径运行,也有足够的时间在消息之间进行一些处理,而不会影响在最后一个回复到达后产生最终结果所需的时间。该时间可以用于将每个发送的消息的一些识别方面插入到集合中并在每个相应的答复到达时将其删除。一旦最后一个回复到达,唯一要做的就是从集合中删除它的标识符,在典型的实现中需要O(log k+1)。之后,该集合包含k个缺失元素的列表,并且不需要进行其他处理。

这肯定不是批处理预先生成的数字包的最快方法,因为整个事件运行O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))。但它确实适用于k的任何值(即使它未提前知道),并且在上面的示例中,它以最小化最关键区间的方式应用。

答案 21 :(得分:1)

您可以尝试使用Bloom Filter。将包中的每个数字插入到bloom中,然后遍历完整的1-k集,直到报告每个未找到的数字。这可能无法在所有情况下找到答案,但可能是一个足够好的解决方案。

答案 22 :(得分:1)

另一种方法是使用残差图过滤。

假设我们有数字1至4,而缺少3。二进制表示形式如下,

1 = 001b,2 = 010b,3 = 011b,4 = 100b

我可以创建如下流程图。

                   1
             1 -------------> 1
             |                | 
      2      |     1          |
0 ---------> 1 ----------> 0  |
|                          |  |
|     1            1       |  |
0 ---------> 0 ----------> 0  |
             |                |
      1      |      1         |
1 ---------> 0 -------------> 1

请注意,流程图包含x个节点,而x是位数。边的最大数量为(2 * x)-2。

因此对于32位整数,它将占用O(32)空间或O(1)空间。

现在,如果我删除从1,2,4开始的每个数字的容量,那么我将剩下一个残差图。

0 ----------> 1 ---------> 1

最后,我将运行如下所示的循环,

 result = []
 for x in range(1,n):
     exists_path_in_residual_graph(x)
     result.append(x)

现在结果在result中也包含不缺少的数字(假阳性)。但是当缺少k个元素时, k <=(结果的大小)<= n

我将最后一次浏览给定列表以标记结果是否丢失。

所以时间复杂度将是O(n)。

最后,可以采取节点00011110而不是仅仅减少误报的数量(以及所需的空间) 01

答案 23 :(得分:1)

非常好的问题。我会为Qk使用一组差异。很多编程语言甚至都支持它,比如Ruby:

missing = (1..100).to_a - bag

它可能不是最有效的解决方案,但如果我在这种情况下遇到这样的任务(已知的边界,低边界),那么我会在现实生活中使用它。如果数字的集合非常大,那么我当然会考虑一种更有效的算法,但在那之前,简单的解决方案对我来说已经足够了。

答案 24 :(得分:1)

您可以通过在对称性(组,数学语言)方面考虑它来激发解决方案。无论数字集的顺序如何,答案应该是相同的。如果您要使用k函数来帮助确定缺少的元素,那么您应该考虑具有该属性的函数:对称。函数s_1(x) = x_1 + x_2 + ... + x_n是对称函数的一个例子,但还有其他更高程度的函数。特别要考虑基本对称函数。 2阶的基本对称函数是s_2(x) = x_1 x_2 + x_1 x_3 + ... + x_1 x_n + x_2 x_3 + ... + x_(n-1) x_n,即两个元素的所有乘积之和。类似地,对于3级和更高级的基本对称函数。它们显然是对称的。此外,事实证明它们是所有对称函数的构建块。

您可以通过注意s_2(x,x_(n+1)) = s_2(x) + s_1(x)(x_(n+1))来构建基本对称函数。进一步的思考应该说服你s_3(x,x_(n+1)) = s_3(x) + s_2(x)(x_(n+1))等等,所以可以一次性计算它们。

我们如何判断数组中缺少哪些项目?想想多项式(z-x_1)(z-x_2)...(z-x_n)。如果您输入任何数字0,则评估为x_i。扩展多项式,得到z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n。基本对称函数也出现在这里,这实在不足为奇,因为如果我们对根应用任何排列,多项式应该保持不变。

因此我们可以构建多项式并尝试将其考虑在内,以确定哪些数字不在集合中,正如其他人所提到的那样。

最后,如果我们担心大数字溢出内存(第n个对称多项式的顺序为100!),我们可以进行这些计算mod p其中p是在这种情况下,我们评估多项式mod p并发现当输入是集合中的数字时它再次求值为0,并且当它是一个非零值时它会计算为非零值。 input是一个不在集合中的数字。但是,正如其他人所指出的那样,为了从多项式中取出值取决于k,而不是N,我们必须考虑多项式mod p

答案 25 :(得分:0)

假设一个ArrayList对象(myList)充满了这些数字,其中缺少2个数字x和y。所以可能的解决方案可以是:

int k = 1;
        while (k < 100) {
            if (!myList.contains(k)) {
                System.out.println("Missing No:" + k);
            }
            k++;
        }

答案 26 :(得分:0)

我已经阅读了所有三十个答案,并找到了最简单的一个,即最好使用100个位的数组。但正如问题所言,我们不能使用大小为N的数组,我将使用O(1)空间复杂度和k次迭代(即O(NK)时间复杂度)来解决这个问题。

为使解释更简单,请考虑给我分配了1到15之间的数字,其中有两个数字缺失,即9和14,但我不知道。让袋子看起来像这样:

[8,1,2,12,4,7,5,10,11,13,15,3,6]。

我们知道每个数字在内部都以位的形式表示。 对于直到16的数字,我们仅需要4位。对于直到10 ^ 9的数字,我们将需要32位。但是,让我们专注于4位,然后我们可以对其进行概括。

现在,假设我们拥有从1到15的所有数字,那么在内部,我们将拥有这样的数字(如果我们对它们进行了排序):

0001
0010
0011
0100
0101
0110
0111
1000
1001
1010
1011
1100
1101
1110
1111

但是现在我们缺少两个数字。因此,我们的表示形式将如下所示(显示顺序用于理解,但顺序可以任意):

(2MSD|2LSD)
00|01
00|10
00|11
-----
01|00
01|01
01|10
01|11
-----
10|00
missing=(10|01) 
10|10
10|11
-----
11|00
11|01
missing=(11|10)
11|11

现在,让我们制作一个大小为2的位数组,其中包含具有对应的2个最高有效数字的数字的计数。即

= [__,__,__,__] 
   00,01,10,11

从左右扫描袋子,并填充上面的数组,以使位数组的每个bin都包含数字计数。结果将如下所示:

= [ 3, 4, 3, 3] 
   00,01,10,11

如果所有数字都存在,它将看起来像这样:

= [ 3, 4, 4, 4] 
   00,01,10,11

因此,我们知道缺少两个数字:一个数字的前2位有效数字为10,而一个数字的前2位有效数字为11。现在再次扫描列表,并为低2位的有效位填充一个大小为2的位数组数字。这次,只考虑最高2位有效数字为10的元素。我们将使用以下位数组:

= [ 1, 0, 1, 1] 
   00,01,10,11

如果存在所有数量的MSD = 10,则所有箱中将只有1,但是现在我们看到其中一个缺失。因此,我们缺少其MSD = 10和LSD = 01的数字为1001,即9。

类似地,如果我们再次扫描但仅考虑MSD = 11的元素,则会得到MSD = 11和LSD = 10的缺失值,即1110,即14。

= [ 1, 0, 1, 1] 
   00,01,10,11

因此,我们可以在恒定的空间中找到缺失的数字。我们可以将其概括为100、1000或10 ^ 9或任何一组数字。

参考文献:http://users.ece.utexas.edu/~adnan/afi-samples-new.pdf中的问题1.6

答案 27 :(得分:0)

我们大多数时间都可以在 O(log n)中执行 Q1和Q2

假设我们的memory chipntest tubes数组组成。试管中的x数字由x milliliter化学液体代表。

假设我们的处理器是laser light。当我们点亮激光时,它会垂直穿过所有管子的长度。每次通过化学液体时,光度都会降低1。并且以一定毫升标记传递光是O(1)的操作。

现在,如果我们将激光照射在试管中间并获得光度输出

  • 等于预先计算的值(在没有数字丢失时计算),然后缺失的数字大于n/2
  • 如果我们的输出较小,则至少有一个缺失的数字小于n/2。我们还可以检查亮度是否会降低12。如果它减少1,那么一个缺失的数字小于n/2,而其他数字大于n/2。如果它减少2,则两个数字都小于n/2

我们可以一次又一次地重复上述过程,缩小我们的问题域。在每一步中,我们将域缩小一半。最后我们可以得到我们的结果。

值得一提的并行算法(因为它们很有趣),

  • 通过某种并行算法排序,例如,并行合并可以在O(log^3 n)时间内完成。然后可以在O(log n)时间内通过二分查找找到丢失的数字。
  • 理论上,如果我们有n个处理器,那么每个进程都可以检查其中一个输入并设置一些标识数字的标志(方便地在数组中)。在下一步中,每个进程都可以检查每个标志,最后输出未标记的数字。整个过程需要O(1)时间。它还有O(n)空间/内存要求。

请注意,上面提供的两个并行算法可能需要额外的空间,如评论中所述。

答案 28 :(得分:0)

动机

如果您想解决一般问题,并且可以存储和编辑数组,那么Caf's solution是迄今为止最有效的方法。如果您无法存储数组(流版本),那么sdcvvc's answer是当前建议的唯一解决方案类型。

我建议的解决方案是最有效的答案(到目前为止,在该线程上),如果您可以存储问题但无法对其进行编辑,那么我从Svalorzen's solution那里得到了一个想法,它可以解决1或2丢失物品。此解决方案需要Θ(k*n)时间,O(k)Ω(log(k))空间-可能实际上是O(min(k,log(n)))空间。它在并行性方面也很好用。

概念

这个想法是,如果您使用比较和的原始方法:
int sum = SumOf(1,n) - SumOf(array)

...然后取缺失数字的平均值:
average = sum/array_size

...提供了一个边界:在缺失的数字中,保证至少有 个数字小于或等于average 至少大于average一个数字。这意味着我们可以分为一些子问题,每个子问题都扫描数组[O(n)],而只关心它们各自的子数组。

代码

C风格的解决方案(不要用全局变量来判断我,我只是想使代码对非c语言人员可读):

#include "stdio.h"

// Example problem:
const int array [] = {0, 7, 3, 1, 5};
const int N = 8; // size of original array
const int array_size = 5;

int SumOneTo (int n)
{
    return n*(n-1)/2; // non-inclusive
}

int MissingItems (const int begin, const int end, int & average)
{
    // We consider only sub-array where elements, e:
    // begin <= e < end
    
    // Initialise info about missing elements.
    // First assume all are missing:
    int n = end - begin;
    int sum = SumOneTo(end) - SumOneTo(begin);

    // Minus everything that we see (ie not missing):
    for (int i = 0; i < array_size; ++i)
    {
        if ((begin <= array[i]) && (array[i] < end))
        {
            n -= 1;
            sum -= array[i];
        }
    }
    
    // used by caller:
    average = sum/n;
    return n;
}

void Find (const int begin, const int end)
{
    int average;

    if (MissingItems(begin, end, average) == 1)
    {
        printf(" %d", average); // average(n) is same as n
        return;
    }
    
    Find(begin, average + 1); // at least one missing here
    Find(average + 1, end); // at least one here also
}

int main ()
{   
    printf("Missing items:");
    
    Find(0, N);
    
    printf("\n");
}

分析

暂时忽略递归,每个函数调用显然需要O(n)时间和O(1)空间。请注意,sum可以等于n(n-1)/2,因此需要存储n-1的位数增加一倍。最多意味着这意味着我们实际上需要两个额外的元素,而不管数组或k的大小如何,因此,按照常规约定,它仍然是O(1)个空间。

对于k缺少的元素,有多少个函数调用并不太明显,因此我将提供一个直观的视图。您原始的子数组(连接的数组)是完整的数组,其中包含所有k个丢失的元素。我们将以递增的顺序想象它们,其中--代表连接(同一子数组的一部分):

m 1 -m 2 -m 3 -m 4 -(.. )-m k-1 -m k

Find函数的作用是将丢失的元素断开到不同的非重叠子数组中。它保证每个子数组中至少有一个丢失的元素,这意味着断开一个连接。

这意味着无论拆分如何发生,总是需要k-1 Find函数调用来完成查找其中仅缺少一个元素的子数组的工作。

所以时间复杂度为Θ((k-1 + k) * n) = Θ(k*n)

对于空间复杂度,如果我们每次按比例进行划分,那么将得到O(log(k))个空间复杂度,但是如果我们一次只分离一个,则会得出O(k)

讨论

我实际上怀疑我们的空间复杂度较小O(min(k,log(n))),但是很难证明。我的直觉:平均值在分离时表现不佳的地方是存在离群值,但是因为分离会消除该离群值。在普通数组中,元素可以都呈指数不同,但是在这种情况下,它们都受n约束。

答案 29 :(得分:0)

我不知道这是否有效,但我想建议这个解决方案。

  1. 计算100个元素的xor
  2. 计算98个元素的xor(删除2个元素后)
  3. 现在(1的结果)XOR(2的结果)给出了两个缺失的n的xor i..e如果a和b是缺失的元素,则为异或b                                                                                                  4.用你通常的和公式差值方法得到缺失的Nos的总和,让我们说diff是d。
  4. 现在运行一个循环来获得可能的对(p,q),它们都位于[1,100]并且总和为d。

    当获得一对时,检查(3的结果)XOR p = q 如果是,我们就完成了。

    如果我错了,请纠正我,如果这是正确的话还要评论时间复杂性

答案 30 :(得分:0)

这可能听起来很愚蠢,但是,在提交给你的第一个问题中,你必须看到包中的所有剩余数字实际添加它们以使用该等式找到丢失的数字。

所以,既然你可以看到所有的数字,只需查找缺少的数字。当两个数字丢失时也是如此。我觉得很简单。当你看到袋子里剩下的数字时,没有必要使用方程式。

答案 31 :(得分:0)

我相信我有一个O(k)时间和O(log(k))空间算法,假设您有floor(x)log2(x)函数可用于任意大整数:

你有一个k位长整数(因此是log8(k)空格)你添加x^2,其中x是你在包中找到的下一个数字:{{1这需要s=1^2+2^2+...时间(这对面试官来说不是问题)。最后,你得到O(N),这是你正在寻找的最大数字。然后j=floor(log2(s))再次执行以上操作:

s=s-j

现在,您通常没有for (i = 0 ; i < k ; i++) { j = floor(log2(s)); missing[i] = j; s -= j; } 的floor和log2函数 - 位整数,而是双精度。所以?简单地说,对于每2个字节(或1个,3个或4个),您可以使用这些函数来获得所需的数字,但这会为时间复杂度增加2756因子

答案 32 :(得分:0)

我认为这可以概括为:

将S,M表示为算术系列和乘法之和的初始值。

S = 1 + 2 + 3 + 4 + ... n=(n+1)*n/2
M = 1 * 2 * 3 * 4 * .... * n 

我应该考虑计算这个的公式,但这不是重点。无论如何,如果缺少一个数字,您已经提供了解决方案。但是,如果缺少两个数字,那么我们用S1和M1表示新的总数和总数,如下所示:

S1 = S - (a + b)....................(1)

Where a and b are the missing numbers.

M1 = M - (a * b)....................(2)

由于你知道S1,M1,M和S,上面的等式可以找到a和b,即缺失的数字。

现在缺少三个数字:

S2 = S - ( a + b + c)....................(1)

Where a and b are the missing numbers.

M2 = M - (a * b * c)....................(2)

现在你的未知数是3,而你只有两个可以求解的方程式。

答案 33 :(得分:-1)

一种方法是计算素数101的模数。

计算并存储整数1到100的乘积,减去这个数模101.很少exo:结果将是1.

计算并存储所有数字1的总和,最多为100,减少结果模数101.小exo:结果为0.

现在假设行李已删除数字x和y。

计算袋子模101中的所有产品和总和。所以我会知道

的值

a = x + y和 b = x * y

模101.

现在很容易找到x和y模101(用101个元素在有限域上求解二次多边形)。

现在你知道x和y模101.但是因为你也知道x和y小于101,你知道它们的真实值。

答案 34 :(得分:-1)

尝试找到1到50之间数字的乘积:

设为乘积,P1 = 1×2×3×............. 50

当您逐个取出数字时,将它们相乘以便得到产品P2。但是这里缺少两个数字,因此P2 < P1。

两个mising项的乘积,a x b = P1 - P2。

你已经知道了总和,a + b = S1。

从上述两个方程中,通过二次方程求解a和b。 a和b是你缺少的数字。

答案 35 :(得分:-1)

您还可以创建大小为last_element_in_the_existing_array + 1的布尔数组。

for循环中标记现有数组中存在的所有元素true

在另一个for循环中,打印包含false的元素的索引,也就是缺少的元素。

时间复杂度:O(last_element_in_the_existing_array)

空间复杂度:O(array.length)

答案 36 :(得分:-1)

可能的解决方案:

public class MissingNumber {
    public static void main(String[] args) {
        // 0-20
        int [] a = {1,4,3,6,7,9,8,11,10,12,15,18,14};
        printMissingNumbers(a,20);
    }

    public static void printMissingNumbers(int [] a, int upperLimit){
        int b [] = new int[upperLimit];
        for(int i = 0; i < a.length; i++){
            b[a[i]] = 1;
        }
        for(int k = 0; k < upperLimit; k++){
            if(b[k] == 0)
                System.out.println(k);
        }
    }
}

答案 37 :(得分:-1)

您可以使用二进制搜索来查找丢失(或连续)数字的间隔。运行时间应该是(num interval)* log(平均间隔长度)* N.如果间隔不多,则有用。

答案 38 :(得分:-1)

在大约O(N)时间内执行此操作的一种非常简单的方法是在两个列表中查看时删除每个元素。这也适用于未排序的列表,如果列表都已排序,则可以轻松进一步优化。

import random

K = 2
missingNums = range(0, 101)
incompleteList = range(0, 101)

#Remove K numbers
for i in range(K):
    valueToRemove = random.choice(incompleteList)
    incompleteList.remove(valueToRemove)

dummyVariable = [missingNums.remove(num) for num in p if num in missingNums]

print missingNums

答案 39 :(得分:-2)

// Size of numbers
def n=100;

// A list of numbers that is missing k numbers.
def list;

// A map
def map = [:];

// Populate the map so that it contains all numbers.
for(int index=0; index<n; index++)
{
  map[index+1] = index+1;  
}

// Get size of list that is missing k numbers.
def size = list.size();

// Remove all numbers, that exists in list, from the map.
for(int index=0; index<size; index++)
{
  map.remove(list.get(index));  
}

// Content of map is missing numbers
println("Missing numbers: " + map);

答案 40 :(得分:-2)

关键是使用索引来标记范围内是否存在数字。 在这里我们知道我们有1到N. 时间复杂度O(n) 空间复杂度O(1)

后续问题: 这可以被修改以查找差异为d的AP是否缺少元素。其他变体可能包括从包含-ve数的任何随机数组中找到第一个缺失+ ve数。然后首先分区大约0 快速排序,然后在分区右侧 部分数组执行此过程,做必要的修改。

public static void  missing(int [] arr){        
      for(int i=0; i< arr.length; i++){       
          if(arr[i]!=-1 && arr[i]<=arr.length){
              int idx=i;
              while(idx>=0 && idx<arr.length&& arr[idx]!=-1 ){
                   int temp =arr[idx];
                   // temp-1 because array index starts from 0, i.e a[0]=-1 is indicates that 1 is present in the array
                   arr[temp-1]=-1;
                   idx=temp-1;
              }
          }
      }
    }

在此之后我们需要遍历数组,并检查是否[i]!= - 1,然后i + 1是缺失的数字。当[i]&gt; N。

时我们必须小心

答案 41 :(得分:-2)

让我们假设它是一个从1到N的数组,其元素是a1,a2,....,aN:

1+N=N+1;
2+N-1=N+1;

..... 所以这里的总和是独一无二的。我们可以从开始和结束扫描数组以添加两个元素。如果总和是N + 1;那好吧,否则他们就不见了。

for (I <= N/2) {
    temp = a[I] + a[n-I];
    if (temp != N+1) then
        Find the missing number or numbers
}

迭代这个循环,你很容易得到答案。

答案 42 :(得分:-2)

我们可以使用以下简单代码来查找重复和缺失值:

    int size = 8;
    int arr[] = {1, 2, 3, 5, 1, 3};
    int result[] = new int[size];

    for(int i =0; i < arr.length; i++)
    {
        if(result[arr[i]-1] == 1)
        {
            System.out.println("repeating: " + (arr[i]));
        }
        result[arr[i]-1]++;
    }

    for(int i =0; i < result.length; i++)
    {
        if(result[i] == 0)
        {
            System.out.println("missing: " + (i+1));
        }
    }

答案 43 :(得分:-2)

如果一个数字只出现一次,则很容易通过以下方式判断:

创建一个大小为给定数字的布尔数组boolArray;这是100。

循环输入数字并根据数字值将元素设置为true。例如,如果找到45,则设置boolArray[45-1] = true;

这将是O(N)操作。

然后循环boolArray。如果元素保持为false,则元素+ 1的索引是缺失的数字。例如,如果boolArray[44]为false,则我们知道缺少45号。

这是O(n)操作。空间复杂度为O(1)。

因此,此解决方案可以查找给定连续数字集中的任何缺失数字。

答案 44 :(得分:-3)

免责声明:我已经阅读了这个问题好几天了,但理解数学并不是我所知。

我尝试使用set:

解决它
arr=[1,2,4,5,7,8,10] # missing 3,6,9
NMissing=3
arr_origin = list(range(1,arr[-1]+1))

for i in range(NMissing):
      arr.append(arr[-1]) ##### assuming you do not delete the last one

arr=set(arr)
arr_origin=set(arr_origin)
missing=arr_origin-arr # 3 6 9

答案 45 :(得分:-4)

我使用Java 8和Java 8编写代码。 它使用公式:(N *(N + 1))/ 2表示所有数字的总和。

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

   /**
 * 
 * 
 * @author pradeep
 * 
 *         Answer : SumOfAllNumbers-SumOfPresentNumbers=Missing Number;
 * 
 *         To GET SumOfAllNumbers : Get the highest number (N) by checking the
 *         length. and use the formula (N*(N+1))/2
 * 
 *         To GET SumOfPresentNumbers: iterate and add it
 * 
 * 
 */
public class FindMissingNumber {
    /**
     * Before Java 8
     * 
     * @param numbers
     * @return
     */
    public static int missingNumber(List<Integer> numbers) {
        int sumOfPresentNumbers = 0;
        for (Integer integer : numbers) {
            sumOfPresentNumbers = sumOfPresentNumbers + integer;
        }
        int n = numbers.size();
        int sumOfAllNumbers = (n * (n + 1)) / 2;
        return sumOfAllNumbers - sumOfPresentNumbers;
    }
    /**
     * Using Java 8 . mapToInt & sum using streams.
     * 
     * @param numbers
     * @return
     */
    public static int missingNumberJava8(List<Integer> numbers) {
        int sumOfPresentNumbers = numbers.stream().mapToInt(i -> i).sum();
        int n = numbers.size();
        int sumOfAllNumbers = (n * (n + 1)) / 2;
        return sumOfAllNumbers - sumOfPresentNumbers;
    }
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list = Arrays.asList(0, 1, 2, 4);
        System.out.println("Missing number is :  " + missingNumber(list));
        System.out.println("Missing number using Java 8 is : " + missingNumberJava8(list));
    }
}*

答案 46 :(得分:-4)

这是一个非常简单的问题

void findMissing(){
    bool record[N] = {0};
    for(int i = 0; i < N; i++){
        record[bag[i]-1] = 1;
    }
    for(int i = 0; i < N; i++){
        if(!record[i]) cout << i+1 << endl;
    }
}

O(n)时间和空间复杂度

答案 47 :(得分:-4)

    //sort
    int missingNum[2];//missing 2 numbers- can be applied to more than 2
    int j = 0;    
    for(int i = 0; i < length - 1; i++){
        if(arr[i+1] - arr[i] > 1 ) {
            missingNum[j] = arr[i] + 1;
            j++;
        }
    }

答案 48 :(得分:-4)

对于k的不同值,方法将是不同的,因此就k而言将不存在通用答案。例如,对于k = 1,可以利用自然数之和,但是对于k = n / 2,必须使用某种比特集。对于k = n-1,同样的方法,可以简单地将包中的唯一数字与其余数字进行比较。