按0和1的顺序查找1的数字

时间:2014-07-02 13:06:30

标签: arrays time-complexity

这是一个面试问题。最初我发现它非常容易和愚蠢。最后我没能解决这个问题。 :(

问题是,我们有一个数组,其序列为0,后跟1的序列和0的序列。像这样的东西。

   int [] arr ={0,0,0,1,1,1,0,0,0}

现在,在log(n)时间内找到数组中的1的数量。

AnyIdea? :(

4 个答案:

答案 0 :(得分:6)

好吧,怎么把这个......

不能。您目前对阵列只有三个假设:

  • 它不以1
  • 开头
  • 它不以1
  • 结尾
  • 对于任意两个1,它们之间没有0

为了在数组中查找内容,您可以使用线性搜索和二进制 * 搜索。线性搜索似乎不合适,因为您希望实现对数时间。 但是,对于二进制搜索,您需要arr[i] <= arr[j]所有i < j。由于情况并非如此,您不知道哪个一半包含1。这意味着您需要检查双方,这会导致线性复杂性。

为什么n-ary(n&gt; = 2)搜索不起作用

那为什么没有任何二进制搜索工作?在回答这个问题之前,让我们快速介绍二进制搜索实际上是如何工作的,因为似乎有点混乱:

二进制搜索如何工作?

二进制搜索工作得很好,因为它可以大大减少问题的大小:在每次迭代中,问题大小减半。

例如,假设我们在有序序列5中寻找000011223334456

initial search tree

这很有效,因为我们得到以下序列:我们首先检查中间,即2。因此我们知道解决方案在右侧,我们永远不必检查左侧。右侧中间是四个,所以我们可以再次切断左半边。下一个中间是5.我们停下来:

search going on

在图像中,永远不会检查红色部分。 从不。对于复杂性,让 n 成为我们原始的问题大小。完成一个步骤后,我们的问题的大小为n/2。在 k 步骤之后,它具有尺寸 n /(2 ^ k)。因此,如果 k = log 2 (n),我们将问题减少到第一个。由于我们在每一步中仅检查了一个值,并且我们总共有 log 2 (n)步骤(向上舍入到下一个积分),我们有对数时间复杂度。

为什么二进制搜索在您的情况下不起作用

简短的回答是:因为您的问题没有排序。让我们尝试使用二进制搜索:

oh oh

一步后会发生什么?

middle

检查中间值根本不会给我们任何信息,除非我们知道它不是1。我们不知道是否需要遍历左侧或右侧树。因此,我们需要遍历两者。为了创建确定性算法,我们需要修复所有应用程序的遍历顺序。如果我们从右到左遍历,我们发现它相对较快:

whoops

但如果1位于左侧,我们会检查几乎所有元素。您还注意到我们不能排除真实二进制搜索中的节点数。

顺便说一下,交替变体也会遇到同样的问题,因为交替只意味着基于水平的遍历。您可以根据节点检查节点,而不是跟踪路径:

alternating

一些评论建议在两棵树中并行/同时搜索。虽然这实际上减少了总时间,但时间复杂度是在图灵机的意义上衡量的。在某个时刻,你将耗尽条带或CPU。请记住,这是关于理论计算时间复杂度。

为什么找到一个1?

如此重要?

如果在对数时间内找不到单个值,则无法找到一对值,例如(0,1)在对数时间内。但是如果你知道单个1的位置,那么左侧和右侧都是有序集,因为它们是000....011..1111....1100...00,并且可以使用二进制搜索。

那么实际上可以在对数时间内解决它吗?

在所有讨论之后,应该清楚我们需要线性运行时来查找单个1。

然而,与假设一起,我们可以在对数时间内找到边缘并减去它们的位置:

  • 数组位置1 k(您的示例建议size/2处有一个

如果您想知道这有何帮助,请查看上一节。

然而,没有额外的假设就无法完成。


*或任何其他n-ary搜索(n> 2),但它们都具有对数成本

答案 1 :(得分:3)

我能想出的最佳解决方案:

  1. 使用随机探测或随机散列探测在数组中找到1
  2. 从那里使用二进制搜索来查找数组中的第一个和最后一个
  3. 步骤2是O(log n)。问题是步骤1是O(n / k),其中k是阵列中1的数量。如果1的数量以任何方式不受限制,则为O(n),但如果相反,则需要1s的数量为阵列的特定部分(至少10%或至少1%或任何下限线性) n),然后它变成O(1)平均情况(尽管仍然是O(n)最坏的情况)

答案 2 :(得分:1)

如果我们能够找到复杂度为O(n)的示例,那么复杂性不是O(log n)的证据就是一个。

考虑一个只包含一个的数组1.计算1s的问题将等于定位单个问题1.在探测数组时,除了它是1还是0之外,我们不会得到额外的信息。不知道我们是否在1之前或之后进行了探测。这意味着为了保证我们找到1,我们必须探测数组n次,O(n)。

因此不是O(log n)。

答案 3 :(得分:0)

你说,

  

我们有一个数组,其序列为0,后跟1的序列和0的序列。

由于您的数组以该形式排列,因此有一种方法可以在少于O(N)的时间内获得数组中的1的数量。

有三个步骤:

  1. 我们知道所有的都在中间。在包含的数组中查找索引1.将索引称为pivot。我正在使用一种我没有名字的方法。这是逻辑:

    1.1。检查array[N/2]是否为1。如果是这样,停下来。 N/2是支点。

    1.2。检查array[N*1/4]array[N*3/4]的值。如果其中任何一个为1,请停止。我们找到1的第一个索引是枢轴。

    1.3检查array[N*1/8]array[N*3/8]array[N*5/8]array[N*7/8]的值。如果其中任何一个为1,请停止。我们找到1的第一个索引是枢轴。

    1.4。继续以这种方式搜索,直到找到具有1的索引。

    我没有确定此算法复杂性的背景,但我认为它比O(N)更好。

  2. 我们知道1的开头必须在[0:pivot]范围内。使用二分法(log(N)操作)找到开始。调用1开始的索引begin

  3. 我们知道1的结尾必须在[pivot+1:end-1]范围内。使用二分法(log(N)操作)查找结尾。在枢轴end之后调用0开始的索引。

  4. 数组中的1的数量为end-begin

    这是代码的一个版本:

    #include <stdio.h>
    #include <iostream>
    #include <algorithm>
    #include <stdlib.h>
    #include <time.h>
    #include <assert.h>
    
    int findBegin(int* begin, int* end, int startIndex)
    {
       std::cout << "*begin: " << *begin << ", *(end-1): " << *(end-1) << ", startIndex: " << startIndex << ", distance: " << (end-begin) << std::endl;
    
       if ( (end - begin) < 2 )
       {
          assert(*begin == 1);
          return startIndex;
       }
    
       int half = (end-begin)/2;
       if ( *(begin+half-1) == 1 )
       {
          return findBegin(begin, begin+half, startIndex);
       }
       else
       {
          return findBegin(begin+half, end, startIndex+half);
       }
    }
    
    int findEnd(int* begin, int* end, int startIndex)
    {
       std::cout << "*begin: " << *begin << ", *(end-1): " << *(end-1) << ", startIndex: " << startIndex << ", distance: " << (end-begin) << std::endl;
    
       if ( (end - begin) < 2 )
       {
          assert(*begin == 1);
          return startIndex+1;
       }
    
       int half = (end-begin)/2;
       if ( *(begin+half) == 0 )
       {
          return findEnd(begin, begin+half, startIndex);
       }
       else
       {
          return findEnd(begin+half, end, startIndex+half);
       }
    }
    
    int findAOne(int* begin, int startIndex, int size)
    {
       int start = size/2;
       int step = size;
       while ( true )
       {
          for ( int i = start; i < size; i += step )
          {
             std::cout << "Checking for 1 at index: " << i << std::endl;
             if ( begin[i] == 1 )
             {
                return i;
             }
          }
          start = start/2;
          step = step/2;
       }
    
       // Should not come here unless there are no 1's in the array.
       return -1;
    }
    
    void findOnes(int array[], int N)
    {
       int pivot = findAOne(array, 0, N);
       if ( pivot < 0 )
       {
          return;
       }
    
       std::cout << "Index where 1 is found: " << pivot << "\n\n";
    
       int begin = findBegin(array, array+pivot+1, 0);
       std::cout << "Done looking for begin\n\n";
    
       int end = findEnd(array+pivot+1, array+N, pivot);
       std::cout << "Done looking for end\n\n";
    
       // Print the bounds of the 1's that we found.
       std::cout << "begin of 1's found: " << begin << std::endl;
       std::cout << "end of 1's found: " << end << std::endl;
    }
    
    void fillData(int array[], int N)
    {
       srand(time(NULL));
       int end1 = rand()%N;
       int end2 = rand()%N;
       int begin = std::min(end1, end2);
       int end = std::max(end1, end2);
    
       // Print the bounds of where the 1's are filled.
       std::cout << "begin of 1's filled: " << begin << std::endl;
       std::cout << "end of 1's filled: " << end << std::endl;
    
       for ( int i = 0; i != begin; ++i )
       {
          array[i] = 0;
       }
    
       for ( int i = begin; i != end; ++i )
       {
          array[i] = 1;
       }
    
       for ( int i = end; i != N; ++i )
       {
          array[i] = 0;
       }
    }
    
    int main(int argc, char** argv)
    {
       int N = atoi(argv[1]);
       int* array = new int[N];
    
       // Fill the array with 1's in the middle.
       fillData(array, N);
    
       // Find the ones.
       findOnes(array, N);
    
       delete [] array;
    }
    

    以1000000点运行的样本输出:

    begin of 1's filled: 972096
    end of 1's filled: 998629
    Checking for 1 at index: 500000
    Checking for 1 at index: 250000
    Checking for 1 at index: 750000
    Checking for 1 at index: 125000
    Checking for 1 at index: 375000
    Checking for 1 at index: 625000
    Checking for 1 at index: 875000
    Checking for 1 at index: 62500
    Checking for 1 at index: 187500
    Checking for 1 at index: 312500
    Checking for 1 at index: 437500
    Checking for 1 at index: 562500
    Checking for 1 at index: 687500
    Checking for 1 at index: 812500
    Checking for 1 at index: 937500
    Checking for 1 at index: 31250
    Checking for 1 at index: 93750
    Checking for 1 at index: 156250
    Checking for 1 at index: 218750
    Checking for 1 at index: 281250
    Checking for 1 at index: 343750
    Checking for 1 at index: 406250
    Checking for 1 at index: 468750
    Checking for 1 at index: 531250
    Checking for 1 at index: 593750
    Checking for 1 at index: 656250
    Checking for 1 at index: 718750
    Checking for 1 at index: 781250
    Checking for 1 at index: 843750
    Checking for 1 at index: 906250
    Checking for 1 at index: 968750
    Checking for 1 at index: 15625
    Checking for 1 at index: 46875
    Checking for 1 at index: 78125
    Checking for 1 at index: 109375
    Checking for 1 at index: 140625
    Checking for 1 at index: 171875
    Checking for 1 at index: 203125
    Checking for 1 at index: 234375
    Checking for 1 at index: 265625
    Checking for 1 at index: 296875
    Checking for 1 at index: 328125
    Checking for 1 at index: 359375
    Checking for 1 at index: 390625
    Checking for 1 at index: 421875
    Checking for 1 at index: 453125
    Checking for 1 at index: 484375
    Checking for 1 at index: 515625
    Checking for 1 at index: 546875
    Checking for 1 at index: 578125
    Checking for 1 at index: 609375
    Checking for 1 at index: 640625
    Checking for 1 at index: 671875
    Checking for 1 at index: 703125
    Checking for 1 at index: 734375
    Checking for 1 at index: 765625
    Checking for 1 at index: 796875
    Checking for 1 at index: 828125
    Checking for 1 at index: 859375
    Checking for 1 at index: 890625
    Checking for 1 at index: 921875
    Checking for 1 at index: 953125
    Checking for 1 at index: 984375
    Index where 1 is found: 984375
    
    *begin: 0, *(end-1): 1, startIndex: 0, distance: 984376
    *begin: 0, *(end-1): 1, startIndex: 492188, distance: 492188
    *begin: 0, *(end-1): 1, startIndex: 738282, distance: 246094
    *begin: 0, *(end-1): 1, startIndex: 861329, distance: 123047
    *begin: 0, *(end-1): 1, startIndex: 922852, distance: 61524
    *begin: 0, *(end-1): 1, startIndex: 953614, distance: 30762
    *begin: 0, *(end-1): 1, startIndex: 968995, distance: 15381
    *begin: 0, *(end-1): 1, startIndex: 968995, distance: 7690
    *begin: 0, *(end-1): 1, startIndex: 968995, distance: 3845
    *begin: 0, *(end-1): 1, startIndex: 970917, distance: 1923
    *begin: 0, *(end-1): 1, startIndex: 971878, distance: 962
    *begin: 0, *(end-1): 1, startIndex: 971878, distance: 481
    *begin: 0, *(end-1): 1, startIndex: 971878, distance: 240
    *begin: 0, *(end-1): 1, startIndex: 971998, distance: 120
    *begin: 0, *(end-1): 1, startIndex: 972058, distance: 60
    *begin: 0, *(end-1): 1, startIndex: 972088, distance: 30
    *begin: 0, *(end-1): 1, startIndex: 972088, distance: 15
    *begin: 0, *(end-1): 1, startIndex: 972095, distance: 8
    *begin: 0, *(end-1): 1, startIndex: 972095, distance: 4
    *begin: 0, *(end-1): 1, startIndex: 972095, distance: 2
    *begin: 1, *(end-1): 1, startIndex: 972096, distance: 1
    Done looking for begin
    
    *begin: 1, *(end-1): 0, startIndex: 984375, distance: 15624
    *begin: 1, *(end-1): 0, startIndex: 992187, distance: 7812
    *begin: 1, *(end-1): 0, startIndex: 996093, distance: 3906
    *begin: 1, *(end-1): 0, startIndex: 998046, distance: 1953
    *begin: 1, *(end-1): 0, startIndex: 998046, distance: 976
    *begin: 1, *(end-1): 0, startIndex: 998534, distance: 488
    *begin: 1, *(end-1): 0, startIndex: 998534, distance: 244
    *begin: 1, *(end-1): 0, startIndex: 998534, distance: 122
    *begin: 1, *(end-1): 0, startIndex: 998595, distance: 61
    *begin: 1, *(end-1): 0, startIndex: 998625, distance: 31
    *begin: 1, *(end-1): 0, startIndex: 998625, distance: 15
    *begin: 1, *(end-1): 0, startIndex: 998625, distance: 7
    *begin: 1, *(end-1): 1, startIndex: 998625, distance: 3
    *begin: 1, *(end-1): 1, startIndex: 998626, distance: 2
    *begin: 1, *(end-1): 1, startIndex: 998627, distance: 1
    Done looking for end
    
    begin of 1's found: 972096
    end of 1's found: 998628
    

    从输出中可以看出,找到枢轴需要65个步骤。找到数据透视后,搜索beginend的速度很快。