这是一个面试问题。最初我发现它非常容易和愚蠢。最后我没能解决这个问题。 :(
问题是,我们有一个数组,其序列为0,后跟1的序列和0的序列。像这样的东西。
int [] arr ={0,0,0,1,1,1,0,0,0}
现在,在log(n)时间内找到数组中的1的数量。
AnyIdea? :(
答案 0 :(得分:6)
你不能。您目前对阵列只有三个假设:
1
,1
,1
,它们之间没有0
。为了在数组中查找内容,您可以使用线性搜索和二进制 * 搜索。线性搜索似乎不合适,因为您希望实现对数时间。
但是,对于二进制搜索,您需要arr[i] <= arr[j]
所有i < j
。由于情况并非如此,您不知道哪个一半包含1
。这意味着您需要检查双方,这会导致线性复杂性。
那为什么没有任何二进制搜索工作?在回答这个问题之前,让我们快速介绍二进制搜索实际上是如何工作的,因为似乎有点混乱:
二进制搜索工作得很好,因为它可以大大减少问题的大小:在每次迭代中,问题大小减半。
例如,假设我们在有序序列5
中寻找000011223334456
。
这很有效,因为我们得到以下序列:我们首先检查中间,即2。因此我们知道解决方案在右侧,我们永远不必检查左侧。右侧中间是四个,所以我们可以再次切断左半边。下一个中间是5.我们停下来:
在图像中,永远不会检查红色部分。 从不。对于复杂性,让 n 成为我们原始的问题大小。完成一个步骤后,我们的问题的大小为n/2
。在 k 步骤之后,它具有尺寸 n /(2 ^ k)。因此,如果 k = log 2 (n),我们将问题减少到第一个。由于我们在每一步中仅检查了一个值,并且我们总共有 log 2 (n)步骤(向上舍入到下一个积分),我们有对数时间复杂度。
简短的回答是:因为您的问题没有排序。让我们尝试使用二进制搜索:
一步后会发生什么?
检查中间值根本不会给我们任何信息,除非我们知道它不是1
。我们不知道是否需要遍历左侧或右侧树。因此,我们需要遍历两者。为了创建确定性算法,我们需要修复所有应用程序的遍历顺序。如果我们从右到左遍历,我们发现它相对较快:
但如果1
位于左侧,我们会检查几乎所有元素。您还注意到我们不能排除真实二进制搜索中的节点数。
顺便说一下,交替变体也会遇到同样的问题,因为交替只意味着基于水平的遍历。您可以根据节点检查节点,而不是跟踪路径:
一些评论建议在两棵树中并行/同时搜索。虽然这实际上减少了总时间,但时间复杂度是在图灵机的意义上衡量的。在某个时刻,你将耗尽条带或CPU。请记住,这是关于理论计算时间复杂度。
如果在对数时间内找不到单个值,则无法找到一对值,例如(0,1)在对数时间内。但是如果你知道单个1的位置,那么左侧和右侧都是有序集,因为它们是000....011..11
和11....1100...00
,并且可以使用二进制搜索。
在所有讨论之后,应该清楚我们需要线性运行时来查找单个1。
然而,与假设一起,我们可以在对数时间内找到边缘并减去它们的位置:
1
k
(您的示例建议size/2
处有一个如果您想知道这有何帮助,请查看上一节。
然而,没有额外的假设就无法完成。
*或任何其他n-ary搜索(n> 2),但它们都具有对数成本
答案 1 :(得分: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.将索引称为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)更好。
我们知道1的开头必须在[0:pivot]
范围内。使用二分法(log(N)操作)找到开始。调用1开始的索引begin
。
我们知道1的结尾必须在[pivot+1:end-1]
范围内。使用二分法(log(N)操作)查找结尾。在枢轴end
之后调用0开始的索引。
数组中的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
个步骤。找到数据透视后,搜索begin
和end
的速度很快。