Найти число 1 в последовательности 0 и 1

Это был вопрос интервью. Сначала я нашел, что это очень легко и глупо. Наконец-то я этого не смог решить.:(

Вопрос в том, что у нас есть массив, который имеет последовательность 0, за которой следует последовательность 1 и последовательность 0. что-то вроде этого.

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

Теперь найдите число 1 в массиве в log (n) времени.

AnyIdea?:(

Ответ 1

Ну, как положить это & ​​hellip;

Вы не можете. В настоящее время у вас есть только три предположения:

  • он не начинается с 1,
  • он не заканчивается на 1,
  • для любых двух 1 s, между ними нет 0.

Чтобы найти что-то в массиве, вы можете использовать поиск по линейному поиску и двоичный поиск *. Линейный поиск не подходит, поскольку вы хотите достичь логарифмического времени. Однако для двоичного поиска вам нужно arr[i] <= arr[j] для всех i < j. Так как это не так, вы не знаете, какая половина содержит 1. Это означает, что вам нужно будет проверить обе стороны, что приведет к линейной сложности.

Почему n-ary (n >= 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

Некоторые комментарии предлагают параллельный/одновременный поиск в обоих деревьях. В то время как это фактически сокращает общее время, временная сложность измеряется в смысле машин для обработки. И в какой-то момент у вас появятся полосы или процессоры. Помните людей, это примерно теоретическая вычислительная временная сложность.

Почему так важно найти один 1?

Если вы не можете найти ни одного значения в логарифмическом времени, вы не можете найти пару значений, например. (0,1) в логарифмическом времени. Но если вы знаете положение одного 1, то левая сторона и правая часть являются упорядоченными наборами, так как они 000....011..11 и 11....1100...00, и можно использовать двоичный поиск.

Итак, можно ли это решить в логарифмическом времени?

После всего обсуждения должно быть ясно, что нам нужно линейное время выполнения, чтобы найти хотя бы один.

Однако вместе с предположением мы можем найти ребра в логарифмическом времени и вычесть их позиции:

  • массив имеет 1 в позиции k (ваш пример предлагает один в size/2)

Если вам интересно, как это помогает, посмотрите на предыдущий раздел.

Однако, без дополнительного предположения, это невозможно.


* или любой другой n-арный поиск (n > 2), но все они имеют логарифмическую стоимость

Ответ 2

Лучшее решение, которое я могу придумать:

  • Используйте случайное зондирование или случайный хеш-зонд, чтобы найти 1 в массиве
  • Используйте бинарные поиски оттуда, чтобы найти первый и последний 1 в массиве

Шаг 2 представляет собой O (log n) тривиально. Проблема заключается в том, что шаг 1 равен O (n/k), где k - число 1s в массиве. Это O (n), если число 1s неограничено каким-либо образом, но если вместо этого требуется число 1s, чтобы быть определенной долей массива (не менее 10% или по меньшей мере 1% или любая нижняя граница, линейная по n), то он становится O (1) средним случаем (хотя все еще O (n) наихудший случай)

Ответ 3

Одним из доказательств того, что сложность не является O (log n), было бы, если бы мы могли найти пример, где сложность O (n).

Рассмотрим массив, содержащий только один 1. Проблема подсчета 1s будет равна задаче определения этого одиночного 1. При исследовании массива мы не получили бы дополнительной информации, кроме того, является ли это 1 или 0. Мы не знал бы, если бы мы исследовали до или после 1. Это означает, что для гарантии того, что мы найдем 1, нам нужно было бы исследовать массив n раз, O (n).

Следовательно, не O (log n).

Ответ 4

Ты сказал,

мы имеем массив, который имеет последовательность 0, за которой следует последовательность 1 и последовательность 0.

Поскольку ваш массив размещен в этой форме, существует способ получить число 1 в массиве менее чем за время O (N).

Есть три шага:

  • Мы знаем, что все они посередине. Найдите индекс в массиве, который содержит 1. Назовите индекс стержнем. Я использую метод, для которого у меня нет имени. Здесь логика:

    1,1. Проверьте, является ли array[N/2] одним. Если да, остановитесь. 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 start begin.

  • Мы знаем, что конец 1 должен находиться в диапазоне [pivot+1:end-1]. Найдите конец, используя метод деления пополам (операция log (N)). Вызвать индекс, где 0 начинается после поворота end.

Число 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 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 that we found.
   std::cout << "begin of 1 found: " << begin << std::endl;
   std::cout << "end of 1 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 are filled.
   std::cout << "begin of 1 filled: " << begin << std::endl;
   std::cout << "end of 1 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 in the middle.
   fillData(array, N);

   // Find the ones.
   findOnes(array, N);

   delete [] array;
}

Выход прогона пробега с 1000000 точек:

begin of 1 filled: 972096
end of 1 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 found: 972096
end of 1 found: 998628

Как видно из вывода, потребовалось 65 шаги, чтобы найти точку опоры. После обнаружения точки поиска поиск begin и end выполняется быстро.