系统会为您提供一个文件,其中包含32位体系结构上所有可能的数字。该文件中缺少4个数字。找到4个缺失的数字


Answers:


19

无论是面试还是实际工作,您的首要任务都必须是对您有意义的可行解决方案。这通常意味着你应该提供你能想到的,很简单,第一个解决方案,并方便解释。

对我来说,这意味着对数字进行排序并寻找差距。但是,我从事业务系统和Web应用程序的工作。我不摆弄,也不希望我的团队去!

如果您面试的是低层次的,接近金属的工作,“分类”可能会遇到空白的凝视。他们希望您对位之类的东西感到轻松。您的第一个答案应该是:“哦,我要使用位图。” (或位数组或位设置。)

然后,无论哪种方式,即使您给“错误”的解决方案,如果您的面试官(或老板!)坚持不懈,您也可以提出一些改进或替代方案,着眼于经理所关注的特定领域。

  • RAM严重受限?小于512MB?
    在磁盘上将其排序到位。您可以使用任意数量的RAM来优化和/或缓冲排序的块。
  • 有限的时间?
    使用该RAM!正在排序O(n*log(n))。(或O(n)用于整数桶排序!)
  • 可维护性?
    还有什么比排序更容易?
  • 不展示位标志/字段的知识吗?(BitSet/ BitMap/ BitArray
    好吧...继续并使用a BitArray标记“找到的数字”。然后扫描0
  • 可预测的 “实时”复杂度?
    使用位图解决方案。这是对文件的单遍传递,而对BitArray/的另一遍传递BitSet(以查找0)。那是O(n),我想!

管他呢。

解决您实际遇到的问题只需先解决问题,并在必要时使用幼稚的解决方案。不要浪费大家的时间来解决尚不存在的问题。


我不太确定用天真的方法排序40亿个数字的可行性,更不用说在磁盘上了。从来没有尝试过。
Eiko

1
@Eiko好吧……再次,重点是……不要使事情变得过于复杂。第一步是解决问题,即使您是幼稚的,也可以以任何方式解决它。我甚至不能强调挫折的水平你未来的雇主将不得不如果你花时间迭代,以确保你有一个“正确”的解决方案的时候,企业只需要一个解决方案。证明您可以同时做到!证明您可以快速解决问题,然后根据需要确定潜在问题,以进行重构和/或优化。
svidgen

1
@Ewan“因为您在面试中遇到了这个问题”与“每个经理都在寻找一个特定的答案”不同。...当然,只要您表现出解决问题的能力并且不会陷入解决我从未给过您的问题的能力,我当然不会在意您给我的解决方案!
svidgen '16

1
您错过了重点。这个问题及其变化形式出现在编程难题和面试问题书中。这不是由提问者来弥补的。32位的东西本来是不可能通过跟踪数字或排序来完成的。自编写以来,它仅有的计算机变得更快/更大。
伊万

1
@Ewan:您仍然假设您的问题实例具有与OP相同的约束。OP并没有说他的算法必须在32位计算机上运行,​​他甚至没有说它必须在计算机上运行,​​概念上的算法可能是合适的。他也没有说明“所有可能的数字”的含义,因为即使在8位微控制器上也可以进行任意大小的整数数学运算。您所做的很多假设都是要给出绝对的陈述。
whatsisname 2016年

19

由于它是一个文件,因此我假设您可以进行多次通过。首先创建一个由256个计数器组成的数组,遍历文件,并为每个数字递增将计数器索引为该数字的第一个字节。完成后,大多数计数器应位于2 ^ 24,但是1到4个计数器应具有较低的值。这些索引中的每个索引都代表缺失数字之一的第一个字节(如果少于4,则是因为多个缺失数字共享相同的第一个字节)。

对于每个索引,创建另一个包含256个计数器的数组,然后在文件上进行第二次传递。这次,如果第一个字节是之前的值之一,则根据第二个字节在其数组中增加一个计数器。完成后,再次查找小于2 ^ 16的计数器,您将获得丢失数字的第二个字节,每个字节都与它的第一个字节匹配。

对第三个字节再次执行此操作(注意,即使每个字节最多可以跟随4个不同的字节,每次遍历最多需要4个数组)和第四个字节,您已经找到了所有丢失的数字。

时间复杂度- O(n * log n)
空间复杂度- 恒定

编辑:

实际上,我认为n=2^32可以作为参数,但是缺少数字的数量k=4也是一个参数。假设k<<n这意味着空间复杂度为O(k)

更新:

只是为了好玩(并且因为我目前正在尝试学习Rust),所以我在Rust中实现了它:https : //gist.github.com/idanarye/90a925ebb2ea57de18f03f570f70ea1f。我选择使用文本表示形式,因为一个将要用〜2 ^ 32个数字运行...


将所有数字保存在内存中(多次)需要4个字节* 2 ^ 32的内存,这正在推动事情。因此,您更有可能将所有I / O进行四次。但是使用的其他内存非常小,因此工作非常出色。
user949300

1
@ user949300我假设此解决方案是逐个读取文件,而不是一次将整个内容加载到内存中
Richard Tingle

“大多数计数器应为2 ^ 24,但最多4个计数器中的1个应具有较低的值”-错误:可以为0,所有丢失的值共享第一个字节(第二个和第三个也可以)。下一步:您在第二遍中创建了多少个数组?256、256的1到4倍,256的256倍?然后在第三遍和第四遍?
Bernhard Hiller

3
@BernhardHiller该文件包含32位空间中的所有可能数字,除了4个不同的数字。这样,所有的第一个字节都会出现,只有其中的1-4个命中次数会减少。
Lasse V. Karlsen

@ LasseV.Karlsen谢谢,现在我了解了算法。
伯恩哈德·希勒

6

如果是Java,则可以使用BitSet。好吧,其中两个,因为它们不能完全容纳所有32位数字。骨架代码,可能有错误:

BitSet bitsetForPositives = new Bitset(2^31);  // obviously not 2^31 but you get the idea
BitSet bitsetForNegatives = new Bitset(2^31);

for (int value: valuesTheyPassInSomehow) {
  if ((value & 0x80000000) == 0)
     bitsetForPositives.set(value );
  else
     bitsetForNegatives.set(value & ~0x80000000);
}

然后使用 BitSet.nextClearBit()查找缺少的人。

注意在以后添加:

请注意,使用此算法,并行运行耗时部分相当容易。假设原始文件已分为四个大致相等的部分。分配4对BitSet(2GB,仍可管理)。

  1. 有四个并行的线程,每个线程将一个文件处理成自己的一对BitSet。
  2. 完成后,返回到单个线程或Bitsets(琐碎的时间),然后调用nextClearBit四次(也相当琐碎的时间)。

我希望I / O仍然是速率限制的步骤,但是如果神奇地将所有数字都存储在内存中,则可以真正加快速度。


3
@伊丹·艾尔 该解决方案只需要很少的代码,因此减少了编码错误的机会。我很高兴这是时间O(n)。它也不假设/要求通过一个巨大的文件多次通过,因此它比需要多次通过的算法占用更少的空间。请详细说明“噢,亲爱的”的意思。
user949300

2
处理不Integer.MIN_VALUE正确。您可以屏蔽符号位,而不用否定它。
CodesInChaos

1
这种幼稚的方法需要2 ^ 32位= 4 Gib = 512 MiB的位集,即使在32位系统上,这也是适量的RAM。
CodesInChaos

如果选择的语言没有内置位集,请使用字节数组对其进行仿真。例如在C#中:bool GetBit(byte[] byteArray, uint index) { var byteIndex = index >> 3; var bitInByte = index & 7; return (byteArray[byteIndex] >> bitInByte) & 1 != 0; }
CodesInChaos

1
@JoulinRouge(和JacquesB)因此,我们同意这在时间上是线性的,使用适度的(1/2 Gig)RAM,并且只需要进行一次I / O。为我工作。
user949300

5

可以使用一个位数组(真/假)解决此问题。这应该是最有效的结构,它使用数组的索引来保存是否找到该特定数字,从而保存所有数字的答案。

C#

var bArray = new BitArray(Int32.MaxValue);

//Assume the file has 1 number per line
using (StreamReader sr = File.OpenText(fileName))
{
        string s = String.Empty;
        while ((s = sr.ReadLine()) != null)
        {
            var n = int32.Parse(s);
            bArray[n] = true;
        }
}

然后只需遍历数组,对于仍然为假的那些值,它们将不在文件中。

您可以将文件分成较小的块,但是我可以在运行Windows 7(64位)的16.0 GB笔记本电脑上分配完整的int32最大大小数组(2147483647)。

即使我没有运行64位,也可以分配较小的位数组。我将对该文件进行预处理,以创建一组较小的文件,每个文件的范围为[0-64000] [64001-128000],以此类推。其中一些数字适合于可用的环境资源。浏览大文件,并将每个数字写入相应的设置文件。然后处理每个较小的文件。由于进行了预处理,因此需要花费一些时间,但是如果资源有限,则会绕开资源限制。


这似乎无法处理负数。(或者,如果是输入,则设置最高位的无符号整数)。即使在大多数32位系统上,位集的内存也不成问题。
user949300

@ user949300-正确。当使用所有假值初始化数组时,我没有注意到任何大的内存消耗。一个负数需要一个辅助BitArray。也许bArrayNegative =新的BitArrary(Int32.MaxValue)。读取数字后,可以检查它是否为正或负,然后放入适当的位阵列中。感谢您的评论。
乔恩·雷诺

2

由于这是一个面试问题,因此我将向面试官展示一些有关约束的理解。那么,“所有可能的数字”是什么意思?大家都猜真的是0 ... 2 <(32-1)吗?通常的32位体系结构可以使用的不仅仅是32位数字。显然,这只是代表问题。

是要在32位系统上解决它,还是这是数字限制的一部分?例如,典型的32位系统将无法立即将文件加载到RAM。我还要提到的是,由于文件大小的限制,一个32位系统通常将无法包含所有数字的文件。好吧,除非它有一些巧妙的编码,例如“除这四个以外的所有数字”,否则这种问题将被轻松解决。

但是,如果您真的想将问题理解为“给出一个文件,其中所有数字都从0 ... 2 ^(32-1)除少数几个之外,请给我一个缺少的数字”(如果不是,这会很大),然后有很多解决方法。

琐碎但不可行:对于每个可能的数字,请扫描文件并查看文件是否在其中。

拥有512 MB的RAM和单次通过文件:标记从文件读取的每个数字(=该索引处的设置位),然后再对一次RAM进行传递并查看丢失的数字。


1
有一些很好的问题,但是无论32位系统是表示整数,浮点数还是表达式,它仍然只能表示32位中的2 ^ 32值。如果问题是“哦,是的,我们允许128位超长”,那么问题中的32位体系结构“约束”是有意误导的。不过,由于许多规范具有误导性或编写不当,这是一个非常好的问题要问面试官。您的实际解决方案是像我的BitSet。
user949300

@ user949300是-不可能知道面试官在寻找什么。如果他们雇用的最后一个人是“思考之前就被黑客入侵”的家伙,那么您的答案应该不同于“绝对不了解体系结构”或“玩优化游戏”的家伙。:)我以前使用过大的位集(尽管不是在Java中),所以它们自然就进入了我的脑海。并且如果需要(存储桶),也可以将其用于较低的内存。这些位集还可以在线性时间内以512 MB的RAM解决上述注释中的“排序问题”。
Eiko

0

一种容易记住且易于在访谈中表达的方法是使用以下事实:如果您查看所有以N位为单位的数字,则每个位将恰好设置为这些值的一半,而不是另一半。

如果您遍历文件中的所有值并在末尾保留32个值,则最终将得到32个值,它们恰好是(2 ^ 32/2)或略小于该值。最大值(2 ^ 32/2)与总数之差为您在缺失值的每个位置设置的总位数。

一旦有了,就可以确定所有可能的4个值的集合,这些集合可以得出这些总数。鉴于此,您可以再次浏览文件中的值,以检查属于那些组合的任何值。当您找到一个时,将消除包含该值的组合。一旦只剩下一种可能的组合,就可以回答。

例如,使用半字节,您将具有以下值:

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

每个位置设置的总位数为:

7867

从8(4 ^ 2/2)中减去这些值,我们得到:

1021

这意味着有以下4种可能的值集:

1000
0000
0011
0010

1010
0001
0010
0000

(请原谅我,如果我错过了任何事情,我只是看得见的)

然后再次查看原始数字,我们立即找到1010,这意味着第一组是答案。


但是您必须找到4个数字,而不是一个
-freedev

@freedev你是正确的。这就是它的作用。一组四个数字就是一组中的四个数字。
JimmyJames '16

有趣,但是你掩饰了determine all the possible sets of 4 values that could give those totals。我真的认为这是解决方案的重要组成部分,您的答案中缺少该部分。它也会影响时间和空间的复杂性。
Allon Guralnek '16

@AllonGuralnek您说对了。我花了一些时间来解决这个问题,而我却大大低估了在最坏的情况下,多少个4个数字加起来等于一个数字。我认为这是一个可挽救的想法,但是比我在这里提出的要复杂得多。稍后我将更新详细信息。感谢您的反馈。
JimmyJames

0

假设文件按递增的顺序排序:

确保确实包含(2³²-4)个数字。
现在,如果文件完整(或者丢失的4个数字是最后4个),则读取文件中位置N的任何单词将返回匹配值N。

使用二分法搜索位置[0..2³²-4-1)来查找第一个非预期数字X1。
找到第一个丢失的数字后,再次对位置[X1 ..(2³²-4-1)]进行二分查找,以查找第二个丢失的X2:这次,读取位置N处的单词应返回匹配值N-1如果没有更多的遗漏号码(因为您传递了一个遗漏的号码)。
同样,对剩余的两个数字进行迭代。在第三次迭代中,读取位置N处的单词应返回N-2,而在第四次迭代中,应返回N-3。

警告:我尚未对此进行测试。但是我认为它应该起作用。:)

在现实生活中,我同意其他答案:第一个问题将是关于环境的。我们是否有可用的RAM(多少),是直接访问存储设备上的文件,这是单次操作(无需优化)还是关键操作(每个周期计数),是否有可用的外部排序实用程序?等等。
然后找到一个可以接受的妥协方案。这至少表明您在寻找算法之前就开始分析问题。


-2

与所有标准问题一样,解决方案是在面试之前先用谷歌搜索它们。

这个问题和变化形式具有非常明确的“正确”答案,涉及对所有数字进行异或运算。它应该告诉您了解数据库中的索引或其他内容。因此,任何“可能的工作都为零,而不是纸上所说的”为零。

从好的方面来说,这些问题的定义有限,几个小时的修订会使您看起来像个天才。只要记住要假装自己在脑子里锻炼。

编辑。嗯,看来4与XOR有不同的方法

http://books.google.com/books?id=415loiMd_c0C&lpg=PP1&dq=muthukrishnan%20data%20stream%20algorithms&hl=el&pg=PA1#v=onepage&q=muthukrishnan%20data%20stream%20algorithms&f=false

编辑。Downvoters:这是针对OP中所述确切问题的已出版教科书O(n)解决方案。


1
值得注意的是,这本链接的书都是关于流处理的。特别是在约束范围内的流处理。话虽如此,我当然相信这是《任择议定书》所见问题的根源,因为在其他方面,这是微不足道的。更值得注意的是,您尚未真正回答问题。如果您可以令人信服地将其表示为“原始”或“预期”问题并说明解决方案,那么您将获得+1的补偿,但是,这并不能解决任何问题。
svidgen '16

1
这个答案(在采访中)只是表明您已经读过这本书。与您的技能或思维过程无关。您如何在面试之前“搜索所有标准问题 ”?我错过了一些有限的“面试中曾经问过的所有问题”吗?
user949300

1
@ewan还强调了聘请优秀候选人的难度!如果“好”员工为面试问题做好了充分的准备...很难雇用一个可以真正解决我的业务问题的人?
svidgen

1
@ewan要清楚,我在取笑不正确的标点符号。...无论如何,请记住,我这一天也收到了很多工作机会,甚至对这样的标准问题和答案都很无知。现在,作为一名招聘经理,我可以向你保证,我不会想要叙述的答案……尽管,我确实知道有些经理会有不同的需求。
svidgen '16

1
@Ewan如果还没有按预期收到我的口气,我还应该澄清一件事:您应该修改答案,以实际断言链接到的书中的问题是“预定问题”。然后回答问题!...毫无疑问,您得到我的+1以及其他许多信息,并且很高兴为OP提供帮助。
svidgen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.