Знайдіть найменше ціле число, якого немає у списку


87

Цікаве запитання в інтерв’ю, яке використовує мій колега:

Припустимо, вам надано дуже довгий, несортований список непідписаних цілих 64-розрядних чисел. Як би ви знайшли найменше ціле невід’ємне число, якого немає у списку?

ПОСЛІДОВНІ РОБОТИ: Тепер, коли пропонується очевидне рішення шляхом сортування, чи можете ви це зробити швидше, ніж O (n log n)?

ПОДАЛЬШЕННЯ: Ваш алгоритм повинен працювати на комп’ютері, скажімо, з 1 Гб пам’яті

ПОЯСНЕННЯ: Список знаходиться в оперативній пам’яті, хоча він може споживати велику кількість. Вам заздалегідь дають розмір списку, скажімо N.


6
Я думаю, ви можете залишити невід’ємну частину, побачивши, як ви говорите про ціле число без знака.
KevenDenen

4
Питання досить базове, за винятком випадків, коли я перебуваю поза базою, ІМО, але, як зазначали інші, є запитання, чи слід висловити припущення.
James Black

8
@paxdiablo: Це випадок, коли сказати O (n) не означає так багато. Навіть якщо ви зберігаєте свій 2 ^ 64-бітний масив на глиняних табличках на острові Пасхи і отримуєте до нього доступ голубом-носієм, алгоритм все одно O (n).
Ай Джей Кеннеді,

6
Зміна вимог до пам’яті наполовину робить це чудове запитання для інтерв’ю ;-)
Кріс Балланс,

1
Думаю, забавно, що всі відповіді мають однакове загальне рішення (відсортувати масив і знайти перше значення, яке порушує послідовність), але всі вони використовують різне сортування. (Модифікована швидка сортування, сортування за радіусом, ...) Прийнята відповідь еквівалентна підрахунку сорту, який відкидає елементи над N.
Йорен

Відповіді:


121

Якщо структуру даних можна змінити на місці та підтримує довільний доступ, тоді ви можете зробити це за час O (N) та додатковий простір O (1). Просто пройдіться по масиву послідовно і для кожного індексу запишіть значення в індексі до індексу, вказаного значенням, рекурсивно розміщуючи будь-яке значення в цьому місці на своєму місці і викидаючи значення> N. Потім знову пройдіть через масив, шукаючи місце де значення не збігається з індексом - це найменше значення, що не є в масиві. Це призводить до порівняння не більше 3N і використовує лише кілька значень, що вартують тимчасового простору.

# Pass 1, move every value to the position of its value
for cursor in range(N):
    target = array[cursor]
    while target < N and target != array[target]:
        new_target = array[target]
        array[target] = target
        target = new_target

# Pass 2, find first location where the index doesn't match the value
for cursor in range(N):
    if array[cursor] != cursor:
        return cursor
return N

9
Маленький нітпік. Ви пропустили тривіальний випадок: коли список дорівнює {0, ..., N-1}. У цьому випадку пропуск 1 нічого не робить, а пропуск 2 масив [курсор] == курсор для всіх записів у списку, тому алгоритм не повертається. Отже, вам потрібен оператор „return N” наприкінці.
Алекс

12
Ваше рішення поєднує домен і діапазон (ціль - це і значення, і індекс). Діапазон обмежений доступним сховищем до 128 млн елементів, однак домен має розмір 2G. Це не вдасться з одним записом зі значенням, більшим за кількість записів, які можна виділити в масив. Якщо у питанні не вказано "дуже довго", відповідь елегантна, навіть якщо вона руйнує введені дані. Компроміс у часовому просторі є дуже очевидним у цій проблемі, і рішення O (N) може бути неможливим за передбачених обмежень.
Pekka

2
Другий прохід міг використовувати бінарний пошук замість лінійного пошуку.
user448810

4
Це рішення працює, лише якщо діапазон значень та індекс порівнянні.
Dubby

7
Це буде добре працювати з більшими значеннями. Більші значення можна ігнорувати, оскільки вони не можуть мати нічого спільного з найменшим значенням, що не знаходиться в масиві. У вашому прикладі перший прохід пройде цикл по масиву, ігноруючи всі значення через ціль <N, а потім поверне 0 на першій ітерації другого проходу.
Ants Aasma

89

Ось просте O(N)рішення, яке використовує O(N)простір. Я припускаю, що ми обмежуємо список введення невід’ємними числами і що ми хочемо знайти перше невід’ємне число, якого немає у списку.

  1. Знайдіть довжину списку; припустимо, це так N.
  2. Виділіть масив Nбулевих значень , ініціалізованих для всіх false.
  3. Для кожного числа Xу списку, якщо Xвоно менше N, встановіть для X'thелемента масиву значення true.
  4. Скануйте масив, починаючи з індексу 0, шукаючи перший елемент, який є false. Якщо ви знайдете перший falseза індексом I, тоді Iце відповідь. Інакше (тобто коли всі елементи є true) відповідь є N.

На практиці "масив Nбулевих символів", мабуть, буде кодований як "растрове зображення" або "бітсет", представлене як byteабо intмасив. Зазвичай це використовує менше місця (залежно від мови програмування) і дозволяє сканувати перше, falseщоб зробити його швидше.


Ось як / чому працює алгоритм.

Припустимо, що Nчисла у списку не відрізняються, або що одне або кілька з них більше ніж N. Це означає, що в діапазоні повинно бути принаймні одне число 0 .. N - 1, якого немає в списку. Тож проблема пошуку найменшого відсутнього числа, отже, повинна зводитися до задачі пошуку найменшого відсутнього числа, менше ніжN . Це означає, що нам не потрібно відстежувати числа, більші чи рівні N..., оскільки вони не будуть відповіддю.

Альтернативою попередньому абзацу є те, що список є перестановкою чисел з 0 .. N - 1. У цьому випадку на кроці 3 встановлюються всі елементи масиву true, а на кроці 4 повідомляється, що першим «відсутнім» числом є N.


Обчислювальна складність алгоритму має O(N)відносно невелику константу пропорційності. Він робить два лінійні проходи через список або лише один прохід, якщо відомо, що довжина списку починається з. Немає необхідності представляти утримання всього списку в пам'яті, тому використання асимптотичної пам'яті алгоритму - це саме те, що потрібно для представлення масиву булевих значень; тобто O(N)біти.

(На відміну від цього, алгоритми, які покладаються на сортування в пам’яті або розділення, передбачають, що ви можете представити весь список у пам’яті. У тому вигляді, в якому було задано питання, для цього знадобляться O(N)64-розрядні слова.)


@Jorn коментує, що кроки з 1 по 3 є варіацією підрахунку сортування. У певному сенсі він має рацію, але відмінності суттєві:

  • Для сортування підрахунку потрібен масив (принаймні) Xmax - Xminлічильників, де Xmaxє найбільше число у списку та Xminнайменше число у списку. Кожен лічильник повинен мати можливість представляти N держав; тобто припускаючи двійкове представлення, воно повинно мати цілочисельний тип (принаймні) ceiling(log2(N))біти.
  • Щоб визначити розмір масиву, підрахунку сортування потрібно зробити початковий прохід через список, щоб визначити Xmaxі Xmin.
  • Отже, мінімальна потреба в найгіршому випадку - це ceiling(log2(N)) * (Xmax - Xmin)біти.

На відміну від цього, алгоритм, представлений вище, просто вимагає Nбіти в гірших і найкращих випадках.

Однак цей аналіз приводить до інтуїції, що якщо алгоритм зробив початковий прохід через список, шукаючи нуль (і підраховуючи елементи списку, якщо потрібно), він дав би швидшу відповідь, не використовуючи пробілу, якщо знайшов нуль. Безумовно, варто це зробити, якщо є велика ймовірність знайти хоча б один нуль у списку. І цей додатковий пропуск не змінює загальної складності.


РЕДАКТУВАТИ: Я змінив опис алгоритму на використання "масиву булевих символів", оскільки люди, мабуть, визнали мій оригінальний опис за допомогою бітів та растрових зображень незрозумілим.


3
@ adi92 Якщо крок 3 дає вам растрове зображення з усіма бітами, встановленими на 1, тоді список містить кожне значення від 0 до N-1. Це означає, що найменшим невід’ємним цілим числом у списку є N. Якщо є значення від 0 до N-1, яке НЕ є у списку, тоді відповідний біт не буде встановлено. Отже, найменшим таким значенням є відповідь.
divegeek

4
@ adi92 У вашому прикладі список містив би 300 елементів. Це означає, що якщо є якесь "відсутнє" значення, воно повинно бути менше 300. Запустивши алгоритм, ми створили битове поле з 300 слотами, а потім неодноразово встановлювали біти в слоти 1, 2 та 3, залишаючи всі інші слоти - від 0 до 4 - 299 - чіткі. При скануванні бітового поля ми знайшли би прапор у слоті 0 чітким, тому ми знали б, що 0 є відповіддю.
divegeek

4
Зверніть увагу, що цей алгоритм можна зрозуміти простіше без бітового перекручування: "Створити логічний масив розміром N" і т. Д. Після того, як ви це зрозумієте, перейти до розрядної версії концептуально легко.
Джон Скіт,

2
Даючи абстрактне рішення, використовуйте концептуально найпростіший спосіб, який працює, і не надто спеціалізуйтеся. Ваше рішення вимагає використання (абстрактного) логічного масиву, тому називайте його так. Те, що ви можете реалізувати цей масив за bool[]допомогою растрового зображення, не має значення для загального рішення.
Йорен

2
Я думаю, що це рішення найкраще описати "Використовуйте сортувальний підрахунок, який не враховує елементи вище N, а потім знайдіть перший відсутній елемент, виконавши лінійний пошук з самого початку".
Йорен

13

Оскільки OP зараз вказав, що оригінальний список зберігається в оперативній пам’яті і що комп’ютер має лише, скажімо, 1 Гб пам’яті, я збираюся вийти на кінцівку і передбачити, що відповідь нульова.

1 Гб оперативної пам'яті означає, що в списку може бути не більше 134 217 728 номерів. Але є 2 64 = 18 446 744 073 709 551 616 можливих чисел. Отже, ймовірність того, що в списку є нуль, є 1 на 137 438 953 472.

На відміну від цього, у мене цього року шанси потрапити під блискавку становлять 1 із 700 000. І мої шанси потрапити в метеорит становлять приблизно 1 із 10 трильйонів. Отже, я маю приблизно в десять разів більше шансів бути записаним у науковому журналі через мою передчасну смерть небесним об’єктом, ніж відповідь не нульова.


11
Ваше обчислення виконується лише в тому випадку, якщо значення рівномірно розподілені та вибрані навмання. Вони могли так само добре генеруватися послідовно.
divegeek

1
Ви, звичайно, праві. Але я все про оптимізацію для загального випадку. :)
Barry Brown

10
Отже, які шанси на те, що допитуваного обрать за допомогою цієї відповіді?
Амаргош,

6
Питання не говорить про те, що цифри вибрані рівномірно навмання. Їх вибирає той, хто задає це питання. Враховуючи це, ймовірність того, що 0 потрапить до списку, набагато більша, ніж 1 на 137 438 953 472, ймовірно, навіть більша, ніж 1 на 2. :-)
ShreevatsaR

8
@Amarghosh Відповідь на це питання також нульова.
PeterAllenWebb

10

Як зазначалося в інших відповідях, ви можете зробити сортування, а потім просто сканувати, поки не знайдете пробіл.

Ви можете покращити алгоритмічну складність до O (N) і зберегти простір O (N), скориставшись модифікованим QuickSort, де ви усунете розділи, які не є потенційними кандидатами на утримання прогалини.

  • На першій фазі розділу видаліть дублікати.
  • Після завершення розділення перегляньте кількість елементів у нижньому розділі
  • Чи дорівнює це значення значенню, яке використовується для створення розділу?
    • Якщо так, то це означає, що проміжок знаходиться у верхньому розділі.
      • Продовжуйте швидку сортування, ігноруючи нижній розділ
    • В іншому випадку зазор знаходиться в нижній перегородці
      • Продовжуйте швидку сортування, ігноруючи верхній розділ

Це економить велику кількість обчислень.


Це досить витончено. Припускаємо, що ви можете обчислити довжину розділу менш ніж за лінійний час, що можна зробити, якщо це зберігається разом з масивом розділів. Також передбачається, що вихідний список міститься в оперативній пам'яті.
Barry Brown

2
Якщо ви знаєте довжину списку, ви також можете вибракувати будь-які значення, більші за len (список). За принципом "голубиної нори", будь-які "дірки" повинні бути менше len (список).
divegeek

1
Я не думаю, що це O (n) ... По-перше, я не впевнений, що ви можете видалити дублікати, поки список не буде повністю відсортований. По-друге, хоча ви можете гарантувати відкидання половини простору пошуку за кожну ітерацію (оскільки ви розділили на середню і нижчу точки), ви все одно маєте кілька проходів (залежно від n) над даними, які залежать від n.
paxdiablo

1
paxdiablo: Ви можете створити новий список лише з унікальними значеннями, використовуючи растровий метод, як те, що запропонував Стівен С. Це працює в O (n) часі та просторі. Я не впевнений, чи можна це зробити краще, ніж це.
Nic

9

Щоб проілюструвати одну з підводних каменів O(N)мислення, ось O(N)алгоритм, який використовує O(1)простір.

for i in [0..2^64):
  if i not in list: return i

print "no 64-bit integers are missing"

1
Воля права. Це не O (n), тому що ви насправді маєте тут два цикли, але один неявний. Визначення, чи є значення у списку, є операцією O (n), і ви робите це n разів у циклі for. Це робить його O (n ^ 2).
Nic

6
Nic, Will, це O (n * N), де n - розмір списку, а N - розмір домену (64-бітні цілі числа). Хоча N - величезна кількість, вона все ще є константою, тому формально складність проблеми, як зазначено, O (n).
Ants Aasma

1
Мурахи, я згоден, що це O (n N), але N не є постійним. Оскільки алгоритм закінчує, коли знаходить відповідь, кількість повних ітерацій через зовнішній цикл дорівнює відповідді, яка сама по собі обмежена розміром списку. Отже, O (N n) - це O (n ^ 2) у цьому випадку.
Will Harris

12
Шукати число у списку з N елементів - це явно O (N). Робимо це 2 ^ 64 рази. Хоча великий, 2 ^ 64 - це ПОСТІЙНА. Тому алгоритмом є C * O (N), що все ще O (N).
IJ Kennedy,

3
Я повинен відмовитись від свого попереднього висловлювання; за найсуворішим визначенням ця операція справді O (n).
Nic

8

Оскільки всі числа мають довжину 64 біти, ми можемо використовувати для них сортування за радіусом , яке дорівнює O (n). Відсортуйте їх, а потім скануйте, поки не знайдете те, що шукаєте.

якщо найменше число дорівнює нулю, скануйте вперед, поки не знайдете пробіл. Якщо найменше число не дорівнює нулю, відповідь дорівнює нулю.


Правда, але вимоги до пам'яті можуть стати досить інтенсивними для сортування radix.
PeterAllenWebb

1
Сортування Radix не працює для дуже великих наборів даних. Але розділення та сортування radix можуть працювати.
DarthVader

5

Для просторово ефективного методу, і всі значення різні, ви можете зробити це в просторі O( k )та часі O( k*log(N)*N ). Це просторово, і немає переміщення даних, а всі операції елементарні (додавання віднімання).

  1. встановити U = N; L=0
  2. Спочатку розділіть числовий простір у kрегіонах. Подобається це:
    • 0->(1/k)*(U-L) + L, 0->(2/k)*(U-L) + L, 0->(3/k)*(U-L) + L...0->(U-L) + L
  3. Знайдіть, скільки чисел ( count{i}) у кожному регіоні. ( N*kкроки)
  4. Знайдіть перший регіон ( h), який не заповнений. Це означає count{h} < upper_limit{h}. ( kкроки)
  5. якщо у h - count{h-1} = 1вас є відповідь
  6. встановити U = count{h}; L = count{h-1}
  7. перейти до 2

це можна покращити за допомогою хешування (спасибі за цю ідею Nic).

  1. те саме
  2. Спочатку розділіть числовий простір у kрегіонах. Подобається це:
    • L + (i/k)->L + (i+1/k)*(U-L)
  3. inc count{j} використання j = (number - L)/k (if L < number < U)
  4. знайти перший регіон (h ), в якій немає k елементів
  5. якщо count{h} = 1 h - ваша відповідь
  6. встановити U = maximum value in region h L = minimum value in region h

Це запуститься O(log(N)*N).


Мені дуже подобається ця відповідь. Це було трохи важко читати, але це дуже схоже на те, що було у мене в голові, коли я читав запитання.
Nic

також в якийсь момент було б розумно перейти на таке растрове рішення Стівена К., ймовірно, колиU-L < k
Егон

Це працює не в O (log (N) * N), а в O (N). Ваша відповідь - це узагальнення відповіді @cdiggins, і вона працює в O (N), оскільки сума (1 / k ** i для i в діапазоні (ceil (log_k (n)))) <= 2.
Lapinot

На кожній ітерації, яку ви проходите через O (N) цифри, вона займає загальну кількість ітерацій O (log_k (N)). Звідси O (log_k (N) * N) == O (log (N) * N). Оригінальні номери не сортуються / групуються, і вам потрібно пройти їх усі.
Егон

Але якщо ви розділили вихідний список на k областей (розміром n / k), тоді ви виберете першу область, яка не заповнена. Отже, на наступній ітерації вам потрібно лише розглянути вибраний регіон і розділити його на k нових областей (розміром n / k ** 2) і т. Д. Насправді ви не повторюєте весь список кожного разу (інакше в чому сенс розділення) ?).
Лапінот

3

Я б просто відсортував їх, а потім провів послідовність, поки не знайшов пробіл (включаючи розрив на початку між нулем та першим числом).

З точки зору алгоритму, щось подібне зробить це:

def smallest_not_in_list(list):
    sort(list)
    if list[0] != 0:
        return 0
    for i = 1 to list.last:
        if list[i] != list[i-1] + 1:
            return list[i-1] + 1
    if list[list.last] == 2^64 - 1:
        assert ("No gaps")
    return list[list.last] + 1

Звичайно, якщо у вас набагато більше пам’яті, ніж у процесора, ви можете створити бітову маску з усіх можливих 64-розрядних значень і просто встановити біти для кожного числа у списку. Тоді знайдіть перший 0-біт у цій бітовій масці. Це перетворює це на операцію O (n) з точки зору часу, але досить прокляту з точки зору вимог до пам'яті :-)

Я сумніваюся, що ви могли б покращити O (n), оскільки я не бачу способу зробити це, що не передбачає перегляду кожного числа хоча б один раз.

Алгоритм для цього був би таким:

def smallest_not_in_list(list):
    bitmask = mask_make(2^64) // might take a while :-)
    mask_clear_all (bitmask)
    for i = 1 to list.last:
        mask_set (bitmask, list[i])
    for i = 0 to 2^64 - 1:
        if mask_is_clear (bitmask, i):
            return i
    assert ("No gaps")

З опису, здається, виключається 0 до першого елемента, оскільки він є найменшим у списку. Але, це припущення, яке я зробив, я можу помилитися.
James Black

Мої думки полягали в тому, що якби відсортована послідовність становила 4,5,6, то 0 було б найменшим у списку.
paxdiablo

Я очікую, що 2, 3, 5, відповідь повинна бути 4, але, я можу помилятися.
James Black

Питання, на яке має відповісти ОП. Чи є простір пошуку "усі 64-розрядні цілі числа без підпису" чи "всі числа між найменшим і найвищим у списку"?
paxdiablo

Я погоджуюсь, що в гіршому випадку вам доведеться подивитися хоча б раз, якщо це вже не було відсортовано у бінарному дереві.
James Black

2

Відсортуйте список, перегляньте перший та другий елементи та починайте рухатися вгору, доки не з’явиться пробіл.


Залежить від того, як ви визначаєте, а не у списку.
James Black

@PeterAllenWebb - Будуть, але цифри в довільному порядку, або відсортовані?
James Black

1

Ви можете зробити це за час O (n) та додатковий простір O (1), хоча прихований фактор досить великий. Це не практичний спосіб вирішити проблему, але, тим не менш, це може бути цікаво.

Для кожного беззнакового 64-розрядного цілого числа (у порядку зростання) переглядайте список, поки не знайдете цільове ціле число або не дійдете до кінця списку. Якщо ви дійдете до кінця списку, ціле число є найменшим цілим числом, якого немає у списку. Якщо ви дійшли до кінця 64-розрядних цілих чисел, кожне 64-розрядне ціле число є у списку.

Ось це як функція Python:

def smallest_missing_uint64(source_list):
    the_answer = None

    target = 0L
    while target < 2L**64:

        target_found = False
        for item in source_list:
            if item == target:
                target_found = True

        if not target_found and the_answer is None:
            the_answer = target

        target += 1L

    return the_answer

Ця функція навмисно неефективна, щоб зберегти її O (n). Особливо зверніть увагу, що функція продовжує перевіряти цілі цілі навіть після того, як була знайдена відповідь. Якщо функція повертається, як тільки відповідь була знайдена, кількість разів запущеного зовнішнього циклу буде обмежена розміром відповіді, який обмежений n. Ця зміна зробить час роботи O (n ^ 2), хоча це буде набагато швидше.


Правда. Забавно, як жахливо деякі алгоритми, що представляють собою O (1) простір та O (n) час, зазнають невдачі на практиці з цим питанням.
PeterAllenWebb

1

Дякую egon, swilden та Stephen C за моє натхнення. По-перше, ми знаємо межі значення цілі, оскільки воно не може бути більшим за розмір списку. Крім того, 1 Гб список може містити щонайбільше 134217728 (128 * 2 ^ 20) 64-розрядних цілих чисел.

Частина хешування
Я пропоную використовувати хешування, щоб різко зменшити наш пошуковий простір. По-перше, квадратний корінь розмір списку. Для списку розміром 1 Гб це N = 11586. Встановіть цілочисельний масив розміром N. Перебирайте список і візьміть квадратний корінь * кожного знайденого числа як ваш хеш. У вашій хеш-таблиці збільште лічильник для цього хешу. Потім перегляньте хеш-таблицю. Перший відрік, який ви виявите не рівним його максимальному розміру, визначає ваш новий простір пошуку.

Частина растрових зображень
Тепер налаштуйте звичайну бітову карту, рівну розміру вашого нового простору пошуку, і знову переглядайте вихідний список, заповнюючи растрову карту, коли знаходите кожне число у вашому пошуковому просторі. Коли ви закінчите, перший неустановлений біт у вашому растровому зображенні дасть вам вашу відповідь.

Це буде виконано за O (n) час та O (sqrt (n)) простір.

(* Ви можете використовувати щось на зразок зсуву бітів, щоб зробити це набагато ефективніше, і просто змінювати кількість та розмір сегментів відповідно.)


1
Мені подобається ідея розділити простір пошуку на сегменти Root-N, щоб зменшити обсяг пам'яті, але дублікати у списку порушили б цей метод. Мені цікаво, чи можна це виправити.
PeterAllenWebb

Ви маєте рацію, я нехтував розглядати дублікати записів. Я не впевнений, що з цим можна обійтись.
Nic

1

Ну, якщо в списку чисел є лише одне число, найпростіший спосіб знайти відсутнє число - це підсумувати ряд і відняти кожне значення у списку. Кінцевим значенням є відсутнє число.


Ага. Це ще одне класичне запитання для інтерв’ю.
PeterAllenWebb

1
Ще простіше, ніж XOR - цифри у списку разом, XOR - цифри в діапазоні разом, а XOR - результати разом.
Джон Курлак

1
 int i = 0;
            while ( i < Array.Length)
            {

                if (Array[i] == i + 1)
                {
                    i++;
                }

                if (i < Array.Length)
                {
                    if (Array[i] <= Array.Length)
                    {//SWap

                        int temp = Array[i];
                        int AnoTemp = Array[temp - 1];
                        Array[temp - 1] = temp;
                        Array[i] = AnoTemp;

                    }
                    else
                       i++;



                }
            }

            for (int j = 0; j < Array.Length; j++)
            {
                if (Array[j] > Array.Length)
                {
                    Console.WriteLine(j + 1);
                    j = Array.Length;
                }
                else
                    if (j == Array.Length - 1)
                        Console.WriteLine("Not Found !!");

            }
        }

1

Ми могли б використовувати хеш-таблицю для зберігання чисел. Після закінчення всіх чисел запустіть лічильник від 0 до тих пір, поки ми не знайдемо найнижчого. Досить хороший хеш буде хеш і зберігати в постійний час, і отримує в постійний час.

for every i in X         // One scan Θ(1)
   hashtable.put(i, i);  // O(1)

low = 0;

while (hashtable.get(i) <> null)   // at most n+1 times
   low++;

print low;

Найгірший випадок, якщо nв масиві є елементи, і вони є {0, 1, ... n-1}, і в цьому випадку відповідь буде отримано за n, зберігаючи його O(n).


1

Ось моя відповідь, написана на Java:

Основна ідея: 1- Прокрутити масив, викидаючи повторювані додатні, нулі та від’ємні числа, підсумовуючи решту, отримуючи також максимальне додатне число, і зберігати унікальні додатні числа на карті.

2- Обчислити суму як max * (max + 1) / 2.

3- Знайдіть різницю між сумами, розрахованими на кроках 1 і 2

4- Повторіть цикл від 1 до мінімуму [різниця суми, макс.] І поверніть перше число, якого немає на карті, заповненій на кроці 1.

public static int solution(int[] A) {
    if (A == null || A.length == 0) {
        throw new IllegalArgumentException();
    }

    int sum = 0;
    Map<Integer, Boolean> uniqueNumbers = new HashMap<Integer, Boolean>();
    int max = A[0];
    for (int i = 0; i < A.length; i++) {
        if(A[i] < 0) {
            continue;
        }
        if(uniqueNumbers.get(A[i]) != null) {
            continue;
        }
        if (A[i] > max) {
            max = A[i];
        }
        uniqueNumbers.put(A[i], true);
        sum += A[i];
    }
    int completeSum = (max * (max + 1)) /  2;
    for(int j = 1; j <= Math.min((completeSum - sum), max); j++) {
        if(uniqueNumbers.get(j) == null) { //O(1)
            return j;
        }
    }
    //All negative case
    if(uniqueNumbers.isEmpty()) {
        return 1;
    }
    return 0;
}

0

Як розумно зазначив Стівен С, відповідь має бути числом менше довжини масиву. Тоді я знайшов би відповідь за допомогою двійкового пошуку. Це оптимізує найгірший випадок (тому інтерв'юер не може вловити вас у патологічному сценарії "що якщо"). Під час співбесіди вкажіть, що ви робите це, щоб оптимізувати для найгіршого випадку.

Спосіб використання двійкового пошуку полягає в тому, щоб відняти число, яке ви шукаєте, з кожного елемента масиву та перевірити відсутність негативних результатів.


0

Мені подобається оцінка "вгадай нуль". Якщо числа були випадковими, нуль дуже ймовірний. Якщо "екзаменатор" встановив невипадковий список, то додайте його і вгадайте ще раз:

LowNum=0
i=0
do forever {
  if i == N then leave /* Processed entire array */
  if array[i] == LowNum {
     LowNum++
     i=0
     }
   else {
     i++
   }
}
display LowNum

Найгірший випадок - n * N з n = N, але на практиці n, швидше за все, буде невеликою кількістю (наприклад, 1)


0

Я не впевнений, чи отримав я запитання. Але якщо для списку 1,2,3,5,6 і відсутнє число дорівнює 4, то відсутнє число можна знайти в O (n) за допомогою: (n + 2) (n + 1) / 2- (n + 1) н / 2

EDIT: вибачте, я думаю, я вчора ввечері думав надто швидко. У будь-якому випадку, другу частину насправді слід замінити на суму (список), звідки походить O (n). Формула розкриває ідею, яка лежить в основі: для n послідовних цілих чисел сума повинна бути (n + 1) * n / 2. Якщо відсутнє число, сума буде дорівнює сумі (n + 1) послідовних цілих чисел мінус відсутнє число.

Дякую за те, що вказав на той факт, що я вкладав у свою думку кілька середніх деталей.


1
Я не бачу, на перший погляд, як це працює. У вашому випадку n = 5 і формуляр буде зафіксовано, незалежно від того, яке число в ньому бракувало.
sisve

Саймон: чи не могли б ви зараз зняти голос проти відповідно до мого редагування?
Кодизм

0

Молодці Мурахи Аасма! Я роздумував над відповіддю близько 15 хвилин і самостійно придумав відповідь, подібну до Вашої думки:

#define SWAP(x,y) { numerictype_t tmp = x; x = y; y = tmp; }
int minNonNegativeNotInArr (numerictype_t * a, size_t n) {
    int m = n;
    for (int i = 0; i < m;) {
        if (a[i] >= m || a[i] < i || a[i] == a[a[i]]) {
            m--;
            SWAP (a[i], a[m]);
            continue;
        }
        if (a[i] > i) {
            SWAP (a[i], a[a[i]]);
            continue;
        }
        i++;
    }
    return m;
}

m представляє "поточний максимально можливий вихід, враховуючи те, що я знаю про перші входи i, і не припускаючи нічого іншого щодо значень до введення в m-1".

Це значення m буде повернуто, лише якщо (a [i], ..., a [m-1]) є перестановкою значень (i, ..., m-1). Таким чином, якщо a [i]> = m або якщо a [i] <i або a [i] == a [a [i]], ми знаємо, що m є неправильним висновком і повинен бути принаймні на один елемент нижче. Тож зменшуючи m і замінюючи a [i] на a [m], ми можемо повернутись назад.

Якщо це неправда, але a [i]> i, тоді знаючи, що a [i]! = A [a [i]] ми знаємо, що обмін a [i] на a [a [i]] збільшить кількість елементів на своєму місці.

В іншому випадку значення [i] має дорівнювати i, і в цьому випадку ми можемо збільшувати i, знаючи, що всі значення до цього індексу, включаючи, рівні їх індексу.

Доказ того, що це не може увійти в нескінченний цикл, залишається читачем як вправа. :)


0

Dafny фрагмент з відповідей показує мурах , чому алгоритм в місці може відбутися збій. requiresПопередня умова описує , що значення кожного елемента не повинні виходити за межі масиву.

method AntsAasma(A: array<int>) returns (M: int)
  requires A != null && forall N :: 0 <= N < A.Length ==> 0 <= A[N] < A.Length;
  modifies A; 
{
  // Pass 1, move every value to the position of its value
  var N := A.Length;
  var cursor := 0;
  while (cursor < N)
  {
    var target := A[cursor];
    while (0 <= target < N && target != A[target])
    {
        var new_target := A[target];
        A[target] := target;
        target := new_target;
    }
    cursor := cursor + 1;
  }

  // Pass 2, find first location where the index doesn't match the value
  cursor := 0;
  while (cursor < N)
  {
    if (A[cursor] != cursor)
    {
      return cursor;
    }
    cursor := cursor + 1;
  }
  return N;
}

Вставте код у валідатор із реченням та без нього, forall ...щоб побачити помилку перевірки. Друга помилка - результат того, що верифікатор не зміг встановити умову завершення для циклу Pass 1. Доведення цього залишається тому, хто краще розуміється на інструменті.


0

Ось відповідь на Java, яка не змінює вхідні дані та використовує час O (N) та N бітів, а також невелику постійну загальну частину пам'яті (де N - розмір списку):

int smallestMissingValue(List<Integer> values) {
    BitSet bitset = new BitSet(values.size() + 1);
    for (int i : values) {
        if (i >= 0 && i <= values.size()) {
            bitset.set(i);
        }
    }
    return bitset.nextClearBit(0);
}

0
def solution(A):

index = 0
target = []
A = [x for x in A if x >=0]

if len(A) ==0:
    return 1

maxi = max(A)
if maxi <= len(A):
    maxi = len(A)

target = ['X' for x in range(maxi+1)]
for number in A:
    target[number]= number

count = 1
while count < maxi+1:
    if target[count] == 'X':
        return count
    count +=1
return target[count-1] + 1

Отримав 100% для вищевказаного розчину.


0

1) Фільтр мінус і нуль

2) Сортування / розрізнення

3) Відвідати масив

Складність : O (N) або O (N * log (N))

за допомогою Java8

public int solution(int[] A) {
            int result = 1;
    boolean found = false;
    A = Arrays.stream(A).filter(x -> x > 0).sorted().distinct().toArray();
    //System.out.println(Arrays.toString(A));
    for (int i = 0; i < A.length; i++) {
        result = i + 1;
        if (result != A[i]) {
            found = true;
            break;
        }
    }
    if (!found && result == A.length) {
        //result is larger than max element in array
        result++;
    }
    return result;
}

0

Unordered_set можна використовувати для зберігання всіх додатних чисел, а потім ми можемо перебирати від 1 до довжини unordered_set і бачити перше число, яке не зустрічається.

int firstMissingPositive(vector<int>& nums) {

    unordered_set<int> fre;
    // storing each positive number in a hash.
    for(int i = 0; i < nums.size(); i +=1)
    {
        if(nums[i] > 0)
            fre.insert(nums[i]);
     }

    int i = 1;
    // Iterating from 1 to size of the set and checking 
    // for the occurrence of 'i'

    for(auto it = fre.begin(); it != fre.end(); ++it)
    {
        if(fre.find(i) == fre.end())
            return i;
        i +=1;
    }

    return i;
}

0

Рішення через базовий javascript

var a = [1, 3, 6, 4, 1, 2];

function findSmallest(a) {
var m = 0;
  for(i=1;i<=a.length;i++) {
    j=0;m=1;
    while(j < a.length) {
      if(i === a[j]) {
        m++;
      }
      j++;
    }
    if(m === 1) {
      return i;
    }
  }
}

console.log(findSmallest(a))

Сподіваюся, це комусь допоможе.


0

З python це не найефективніше, але правильно

#!/usr/bin/env python3
# -*- coding: UTF-8 -*-
import datetime

# write your code in Python 3.6

def solution(A):
    MIN = 0
    MAX = 1000000
    possible_results = range(MIN, MAX)

    for i in possible_results:
        next_value = (i + 1)
        if next_value not in A:
            return next_value
    return 1

test_case_0 = [2, 2, 2]
test_case_1 = [1, 3, 44, 55, 6, 0, 3, 8]
test_case_2 = [-1, -22]
test_case_3 = [x for x in range(-10000, 10000)]
test_case_4 = [x for x in range(0, 100)] + [x for x in range(102, 200)]
test_case_5 = [4, 5, 6]
print("---")
a = datetime.datetime.now()
print(solution(test_case_0))
print(solution(test_case_1))
print(solution(test_case_2))
print(solution(test_case_3))
print(solution(test_case_4))
print(solution(test_case_5))

0
def solution(A):
    A.sort()
    j = 1
    for i, elem in enumerate(A):
        if j < elem:
            break
        elif j == elem:
            j += 1
            continue
        else:
            continue
    return j

0

це може допомогти:

0- A is [5, 3, 2, 7];
1- Define B With Length = A.Length;                            (O(1))
2- initialize B Cells With 1;                                  (O(n))
3- For Each Item In A:
        if (B.Length <= item) then B[Item] = -1                (O(n))
4- The answer is smallest index in B such that B[index] != -1  (O(n))

Чи відрізняється це від відповіді Стівена Сі ? Як?
greybeard
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.