Ось просте O(N)рішення, яке використовує O(N)простір. Я припускаю, що ми обмежуємо список введення невід’ємними числами і що ми хочемо знайти перше невід’ємне число, якого немає у списку.
- Знайдіть довжину списку; припустимо, це так
N.
- Виділіть масив
Nбулевих значень , ініціалізованих для всіх false.
- Для кожного числа
Xу списку, якщо Xвоно менше N, встановіть для X'thелемента масиву значення true.
- Скануйте масив, починаючи з індексу
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біти в гірших і найкращих випадках.
Однак цей аналіз приводить до інтуїції, що якщо алгоритм зробив початковий прохід через список, шукаючи нуль (і підраховуючи елементи списку, якщо потрібно), він дав би швидшу відповідь, не використовуючи пробілу, якщо знайшов нуль. Безумовно, варто це зробити, якщо є велика ймовірність знайти хоча б один нуль у списку. І цей додатковий пропуск не змінює загальної складності.
РЕДАКТУВАТИ: Я змінив опис алгоритму на використання "масиву булевих символів", оскільки люди, мабуть, визнали мій оригінальний опис за допомогою бітів та растрових зображень незрозумілим.