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

Easy interview question got harder: given numbers 1..100, find the missing number(s) given exactly k are missing

我有一段很有趣的面试经历。问题开始很容易:

Q1: We have a bag containing numbers 1, 2, 3, …, 100. Each number appears exactly once, so there are 100 numbers. Now one number is randomly picked out of the bag. Find the missing number.

当然,我以前听过这个面试问题,所以我很快就回答了:

A1: Well, the sum of the numbers 1 + 2 + 3 + … + N is (N+1)(N/2) (see Wikipedia: sum of arithmetic series). For N = 100, the sum is 5050.

Thus, if all numbers are present in the bag, the sum will be exactly 5050. Since one number is missing, the sum will be less than this, and the difference is that number. So we can find that missing number in O(N) time and O(1) space.

在这一点上,我认为我做得很好,但突然间问题发生了意想不到的转变:

Q2: That is correct, but now how would you do this if TWO numbers are missing?

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

面试官确实鼓励我说,有第二个方程式确实是解决问题的一种方法。在这一点上,我有点心烦意乱(因为在手前不知道答案),我问这是一种通用的(阅读:"有用的")编程技术,还是仅仅是一个诡计/成功的答案。

面试官的回答让我很惊讶:你可以概括一下找出3个缺失数字的方法。事实上,你可以把它推广到寻找缺少K的数字。

Qk: If exactly k numbers are missing from the bag, how would you find it efficiently?

这是几个月前的事了,我还是搞不清这是什么技术。显然,由于我们必须至少扫描一次所有数字,所以存在一个Ω(N)时间下限,但采访者坚持认为,求解技术的时间和空间复杂性(减去O(N)时间输入扫描)是用k而不是n定义的。

所以这里的问题很简单:

  • 如何解决问题2?
  • 你将如何解决问题3?
  • 你将如何解决问题?

澄清

  • 通常有n个从1到n的数字,而不仅仅是1到100。
  • 我不想寻找明显的基于集合的解决方案,例如使用一个位集,用指定位的值对每个数字的存在/不存在进行编码,因此在额外的空间中使用O(N)位。我们负担不起任何与n成比例的额外空间。
  • 我也不想寻找明显的排序优先方法。这种方法和基于集合的方法在面试中值得一提(它们很容易实现,并且依赖于n,可能非常实用)。我在寻找圣杯解决方案(可能实际实现,也可能不实际实现,但仍然具有所需的渐进特性)。

因此,当然,您必须扫描O(N)中的输入,但是您只能捕获少量的信息(定义为k而不是n),然后必须以某种方式找到缺少k的数字。


下面是Dimitris Andreou链接的摘要。

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

A1A2+AK=B1

A12+A22+…+AK2=B2

A1K+A2K+…+AKK=BK

使用牛顿的恒等式,知道biallows to compute

C1=A1+A2+…A <> K</Sub >

C2=A1A2+A1A3+…+AK-1AK

CK=A1A2A <> K</Sub >

如果展开多项式(x-a1)…(x-ak)系数将精确为c1,…,ck—请参见vi_te公式。由于每个多项式因子都是唯一的(多项式环是欧几里得域),这意味着ai是唯一确定的,直到排列为止。

这就证明了记忆的力量足以恢复数字。对于常数k,这是一个很好的方法。

但是,当k发生变化时,计算c1的直接方法非常昂贵,因为例如ckk是所有缺失数字的乘积,量级n!/(N-K)!为了克服这个问题,在zqfield中进行计算,其中q是一个素数,这样n<=q<2n-它由伯特兰的假设存在。证明不需要改变,因为公式仍然成立,多项式的因式分解仍然是唯一的。您还需要一个有限域上的因子分解算法,例如Berlekamp或Cantor Zassenhaus的算法。

常数k的高级伪代码:

  • 计算给定数字的第i次幂
  • 减法得到未知数的第i次幂和。调用sums bi
  • 使用牛顿恒等式计算bi的系数;称之为ci。基本上,C1=B1;C2=(C1B1-B2)/2;有关精确公式,请参阅维基百科。
  • 系数多项式xk-c1xk-1+…+CK
  • 多项式的根是所需的数字a1、…、ak

对于变化的k,使用Miller-Rabin等方法求出质数n<=q<2n,并在所有数字都减少模q的情况下执行步骤。

编辑:此答案的前一版本说明,可以使用特征2(q=2^(log n))的有限字段,而不是zq,其中q是prime。但事实并非如此,因为牛顿公式需要除以不超过k的数字。


你可以通过阅读muthukrishnan的几页来找到它——数据流算法:难题1:寻找缺失的数字。它精确地显示了您正在寻找的推广。也许这就是你的面试官读到的,也是他提出这些问题的原因。

现在,如果人们开始删除被穆楚克里希南治疗所包含或取代的答案,并使这篇文章更容易找到的话。:)

另请参见SDCVVC的直接相关答案,其中还包括伪代码(hurray!不用看那些复杂的数学公式了:(谢谢,干得好!).


我们可以通过把数字本身和数字的平方相加来解q2。

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

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

其中,xy是总和低于预期值的程度。

替换给了我们:

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

然后我们就可以解决这个问题来确定缺失的数字。


正如@j_random_hacker所指出的,这与在o(n)时间和o(1)空间中查找重复项非常相似,我在这里的答案也适用。

假设"包"由一个基于1的数组A[]表示,尺寸为N - k,我们可以在O(N)时间和O(k)附加空间中求解qk。

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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岁的孩子来解决这个问题。他把数字分类,然后数数。这有一个空间要求的O(厨房地板),它的工作一样容易,但许多球丢失。


不确定,如果这是最有效的解决方案,但是我将循环遍历所有条目,并使用一个位集来记住,设置了哪些数字,然后测试0位。

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


我没有查过数学,但我怀疑在计算Σ(n)的同一个过程中计算Σ(n^2)可以提供足够的信息来得到两个丢失的数字,如果有三个,也可以做Σ(n^3)等等。


基于数字和的解的问题是,它们没有考虑存储和处理具有大指数的数字的成本…在实践中,为了使它适用于非常大的n,将使用一个大数字库。我们可以分析这些算法的空间利用率。

我们可以分析SDCVVC和DimitrisAndreou算法的时空复杂性。

存储:

1
2
3
4
5
6
7
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)时间,总时间:

1
2
3
4
5
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个条目,之前的数字以及k删除的次数。在haskell语法中…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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))该算法更直观,时间复杂度相同,占用空间更小。


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

如果我们把解推广到从1到n的数值,除了n以外没有什么变化不是常数,所以我们是在o(n-k)=o(n)时间内。例如,如果我们使用一个位集,我们在O(n)时间中将位设置为1,迭代这些数字,在我们进行(o(n-k)=O(n))时将位设置为0,然后我们就得到了答案。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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
", i);
    for (int i = n + 1; i <= n + k; ++i)
        if (! extra [i - n - 1]) printf ("Number %d is missing
", 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; }
    }
}


为了解决2(和3)缺少数字的问题,您可以修改quickselect,它平均运行在O(n)中,如果分区到位,则使用常量内存。

  • 将集合相对于随机轴p划分为包含小于轴的数字的分区l和包含大于轴的数字的分区r

  • 通过比较透视值和每个分区的大小(p - 1 - count(l) = count of missing numbers in ln - count(r) - p = count of missing numbers in r

  • 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个数字,但不是空的,则递归到该分区。

  • 由于只有2个丢失的数字,该算法总是丢弃至少一个分区,因此保留了QuickSelect的O(n)平均时间复杂性。同样,对于3个丢失的数字,该算法也会在每次传递时丢弃至少一个分区(因为对于2个丢失的数字,最多只有一个分区包含多个丢失的数字)。但是,我不确定当添加更多缺少的数字时性能会下降多少。

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    <?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) ."
    ";
        }
        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) ."
    ";
        } else if(count($r) < $max - $pivot) {
          // mroe than 1 missing number recurse
          findMissing($r, $pivot+1, $max);
        }
      }

    演示


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

    S = sum of all numbers in the bag (S < 5050) Z = sum of the missing numbers 5050 - S

    如果缺少的数字是xy,那么:

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

    所以你检查一下从1max(x)的范围,找出数字


    可能该算法适用于问题1:

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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    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将位于不同的桶中。现在重复同样的步骤,在每个桶上找到一个丢失的元素。


    如果两个列表的总和和两个列表的积,则可以求解q2。

    (l1为原件,l2为修改单)

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

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

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

    现在我们知道了(如果A和B是删除的数字):

    1
    2
    a + b = d
    a * b = m

    所以我们可以重新安排:

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

    然后乘以:

    1
    -b^2 + s*b = m

    重新排列,使右边为零:

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

    然后用二次公式求解:

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

    python 3代码示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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函数的复杂性,所以我无法计算出这个解决方案的复杂性(如果有人知道,请在下面评论)。


    对于第二季度,这是一个比其他解决方案效率低一点的解决方案,但仍然有O(N)运行时,占用O(K)空间。

    其思想是运行原始算法两次。在第一个例子中,你会得到一个丢失的总数,它给你一个丢失数字的上界。我们把这个号码叫做N。你知道丢失的两个数字加起来是N,所以第一个数字只能在[1, floor((N-1)/2)]的区间内,而第二个数字只能在[floor(N/2)+1,N-1]的区间内。

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

    我有一种感觉,这种方法可以被推广,在输入的一次传递过程中,可能会有多个搜索以"并行"的方式运行,但我还没有弄清楚是如何进行的。


    有一种通用的方法来概括这样的流算法。我们的想法是使用一些随机化的方法,希望能将k元素"传播"到独立的子问题中,在那里我们的原始算法可以为我们解决问题。这项技术被用于稀疏信号重建等。

    • 制作一个数组,a,大小为u = k^2
    • 选择任何通用哈希函数,h : {1,...,n} -> {1,...,u}。(像乘法移位)
    • 每增加一个i,增加a[h(i)] += i
    • 对于输入流中的每个数字x,减少a[h(x)] -= x

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

    根据通用散列函数的定义,特定对发送到同一个bucket的概率小于1/u。由于存在约k^2/2对,我们得出误差概率最多为k^2/2/u=1/2。也就是说,我们成功的概率至少是50%,如果我们增加eDCx1(25),我们就增加了机会。

    请注意,该算法使用k^2 logn位空间(我们需要每个数组桶使用logn位空间),这与@dimitris andreou的答案所需的空间相匹配(特别是多项式因式分解的空间要求,也恰好是随机化的)。该算法在每次更新时也具有恒定的时间,而不是在功率和情况下的时间k

    实际上,通过使用注释中描述的技巧,我们甚至可以比幂和法更有效。


    我认为这可以在没有任何复杂的数学方程和理论的情况下完成。以下是一个就地O(2N)时间复杂性解决方案的建议:

    输入表单假设:

    #袋中数量=n

    #缺失数字的数量=k

    袋子中的数字由长度n的数组表示。

    algo的输入数组长度=n

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

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

    此解决方案的关键是在遍历数组时,通过对该索引处的值求反来标记已访问数字的索引。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
        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;
        }


    你可能需要澄清O(K)的意思。

    对于任意k,这里有一个简单的解决方案:对于你的数字集中的每一个v,求2^v的和。最后,循环i从1到n。如果与2^i相加的和为零,那么我就不见了。(或者用数字表示,如果和的底除以2^i为偶数。或sum modulo 2^(i+1)) < 2^i

    容易,对吧?o(n)时间,o(1)存储,支持任意k。

    除了你在计算大量的数字,在一台真正的计算机上,每一个都需要O(N)空间。实际上,这个解和位向量是相同的。

    所以你可以聪明地计算平方和和和立方体的和…达到v^k的总和,然后进行奇特的数学运算以提取结果。但这些数字也很大,这就引出了一个问题:我们在讨论什么抽象的操作模型?在O(1)空间中适合的大小是多少,以及需要多长时间才能求出所需大小的数字?


    你可以从对称性的角度(群体,数学语言)来思考这个问题,以此来激励解决方案。不管数字的顺序如何,答案都应该是一样的。如果要使用k函数来帮助确定缺少的元素,那么应该考虑哪些函数具有该属性:对称。函数s_1(x) = x_1 + x_2 + ... + x_n是对称函数的一个例子,但也有一些程度较高的函数。特别是考虑初等对称函数。二次的初等对称函数是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。展开多项式,得到z^n-s_1(x)z^(n-1)+ ... + (-1)^n s_n。初等对称函数也出现在这里,这并不奇怪,因为如果对根应用置换,多项式应该保持不变。

    所以我们可以建立多项式,并试着将其因子化,以确定哪些数字不在集合中,正如其他人提到的那样。

    最后,如果我们关心的是大数溢出内存(n次对称多项式的阶数为100!),我们可以计算mod p,其中p是大于100的素数。在这种情况下,我们计算多项式mod p,发现当输入是集合中的数字时,它再次计算为0,当输入是集合中的数字时,它计算为非零值。然而,正如其他人指出的那样,要及时从依赖于k而不是N的多项式中得到值,我们必须将多项式mod p因子化。


    很好的问题。我会用一个设定差来计算QK。许多编程语言甚至支持它,比如Ruby:

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

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


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

    假设我们有1到4的数字,3不见了。二进制表示如下:

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

    我可以创建一个流程图,如下所示。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
                       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开始移除每个数字的容量,那么剩下的就是一个残差图。

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

    最后,我将运行如下循环:

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

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

    我将最后一次查看给定的列表,以标记结果是否丢失。

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

    最后,可以通过取节点00011110,而不是只取01来减少假阳性的数量(以及所需的空间)。


    我会用不同的方法来回答这个问题,并询问面试官关于他试图解决的更大问题的更多细节。根据问题及其周围的需求,显而易见的基于集合的解决方案可能是正确的,而生成一个列表和一个接一个的IT事后方法可能不是。

    例如,面试官可能会发送n条信息,需要知道k,而这些信息不会导致回复,需要在n-k条回复到达后尽可能短的挂钟时间内知道。我们还可以说,消息通道的性质是这样的,即使以全通径运行,也有足够的时间在消息之间进行一些处理,而不会对最后一个答复到达后生成最终结果所需的时间产生任何影响。这段时间可以用来将每个已发送消息的一些标识方面插入到一个集合中,并在每个对应的回复到达时将其删除。一旦最后一个回复到达,唯一要做的就是从集合中删除它的标识符,在典型的实现中,这个标识符接受O(log k+1)。之后,集合中包含了k个缺少元素的列表,并且不需要进行额外的处理。

    这当然不是批处理预先生成的数字包的最快方法,因为整个过程运行O((log 1 + log 2 + ... + log n) + (log n + log n-1 + ... + log k))。但它对k的任何值都有效(即使事先不知道),在上面的示例中,它的应用方式是最小化最关键的间隔。


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

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

    1
    2
    3
    4
    5
    6
    for (i = 0 ; i < k ; i++)
    {
      j = floor(log2(s));
      missing[i] = j;
      s -= j;
    }

    现在,对于2756位整数,通常没有floor和log2函数,而对于double则没有。那么?简单地说,对于每2个字节(或1、3或4),您可以使用这些函数来获得所需的数字,但这会给时间复杂性增加一个O(N)因子。


    我认为可以这样概括:

    表示s,m为算术级数和乘法的初始值。

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

    我应该考虑一个计算这个的公式,但这不是重点。不管怎样,如果缺少一个数字,您已经提供了解决方案。但是,如果缺少两个数字,那么让我们用s1和m1来表示新的和和和总倍数,如下所示:

    1
    2
    3
    4
    5
    S1 = S - (a + b)....................(1)

    Where a and b are the missing numbers.

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

    因为你知道s1,m1,m和s,上面的方程是可以解的,可以找到A和B,缺失的数字。

    现在,对于丢失的三个数字:

    1
    2
    3
    4
    5
    S2 = S - ( a + b + c)....................(1)

    Where a and b are the missing numbers.

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

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


    这听起来可能很愚蠢,但是,在第一个问题中,你必须看到包里剩下的所有数字,才能用这个方程把它们相加,找到丢失的数字。

    所以,既然你能看到所有的数字,只需寻找丢失的数字。当两个数字丢失时,情况也一样。我觉得很简单。当你看到袋子里剩下的数字时,用一个方程是没有意义的。


    你可以试着用布卢姆过滤器。将包中的每个数字插入bloom,然后迭代整个1-k集,直到报告未找到的每个数字。这可能在所有的场景中都找不到答案,但可能是一个足够好的解决方案。


    我们可以在O(logn)中进行q1和q2。

    假设我们的memory chiptest tubesn个数组组成。试管中的一个数字x由化学液体的xmilliliter表示。

    假设我们的处理器是一个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)空间/内存。

    注意,上面提供的两个并行算法可能需要注释中提到的额外空间。


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

  • 计算100个元素的xor
  • 计算98个元素的XOR(删除2个元素后)
  • 现在(1的结果)xor(2的结果)给出了两个缺失的nos的xor,即如果a和b是缺失的元素,则为xor b。4.用通常的求和公式diff的方法求出缺失的nos的和,假设diff是d。
  • 现在运行一个循环,得到可能的对(p,q),这两个对都位于[1,100]中,求和为d。

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

    如果我错了,请纠正我;如果我错了,请评论时间复杂性


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

    让积,p1=1 x 2 x 3 x…………五十

    当你把数字一个一个地拿出来时,把它们相乘,得到产品p2。但是这里缺少两个数字,因此p2

    两个错误项的乘积,a x b=p1-p2。

    你已经知道总数了,a+b=s1。

    从上述两个方程出发,用二次方程求解A和B。A和B是你丢失的号码。


    可能的解决方案:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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);
            }
        }
    }


    一个非常简单的方法,大致在O(n)时间内完成,就是在两个列表中都看到时删除每个元素。这也适用于未排序的列表,如果两个列表都排序,则可以轻松地进一步优化。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    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


    您可以使用二进制搜索来查找丢失(或连续)数字的间隔。运行时间应该在(num interval)*log(avg interval length)*n左右。如果间隔不多,则很有用。


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

    计算并存储整数1到100的乘积,将这个数减去模101。小外显子:结果是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,你就知道它们的真实值。


    如果一个数字只出现一次,那么很容易用以下方式来判断:

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

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

    这将是一个O(N)操作。

    然后循环通过boolArray。如果一个元素保持为假,那么元素+1的索引就是丢失的数字。例如,如果boolArray[44]是假的,我们就知道45号不见了。

    这是一个O(N)操作。空间复杂性为O(1)。

    所以这个解决方案可以从给定的连续数集中找到任何缺失的数。


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

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
        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));
            }
        }

    假设它是一个从1到n的数组,其元素是a1,a2,…,an:

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

    …所以这里的总和是独一无二的。我们可以从开始和结束扫描数组来添加这两个元素。如果总和是n+1,那么好的,否则它们会丢失。

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

    重复这个循环,你就能很容易地得到答案。


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

    后续问题:这可能会被修改,以查找差异d的ap中是否缺少元素。其他变化可能包括从包含-ve数的任意随机数组中查找第一个缺少的+ve数。然后,首先在0quick sort左右分区,然后在分区的右侧执行此过程part of thearray,do necessary modification.

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    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]>N.


    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 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);

    免责声明:这个问题我已经读了好几天了,但我无法理解数学。

    我试图用集合来解决它:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    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


    这个问题很简单

    1
    2
    3
    4
    5
    6
    7
    8
    9
    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)时空复杂性


    1
    2
    3
    4
    5
    6
    7
    8
    9
        //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++;
            }
        }


    我已经用Java 8和Java 8编写了代码。它使用一个公式:(n*(n+1))/2计算所有数字的和。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    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));
        }
    }*


    对于不同的k值,方法会有所不同,因此就k而言,没有一般的答案。例如,对于k=1,可以利用自然数之和,但对于k=n/2,必须使用某种位集。同样,对于k=n-1,我们可以简单地将袋子中的唯一数字与其余数字进行比较。