Автор оригинала: François Dupire.
1. Обзор
В этом учебнике мы увидим различные алгоритмы, позволяющие нам найти самый маленький недостающий положительный целый ряд в массиве.
Во-первых, мы пройдемся по объяснению проблемы. После этого мы увидим три различных алгоритма, которые подойдут нашим потребностям. Наконец, мы обсудим их сложности.
2. Объяснение проблемы
Во-первых, давайте объясним, какова цель алгоритма. Мы хотим найти самый маленький недостающий положительный целый ряд в массиве положительных целых. То есть, в массиве x элементы, найти самый маленький элемент между 0 и x – 1 этого нет в массиве. Если массив содержит их все, то решение x , размер массива.
Например, рассмотрим следующий массив: 0, 1, 3, 5, 6 . Он 5 азы. Это означает, что мы ищем самый маленький integer между 0 и 4 этого нет в этом массиве. В данном конкретном случае, это 2 .
Теперь давайте представим себе другой массив: (0, 1, 2, 3) . Как и 4 элементы, мы ищем интегратор между 0 и 3 . Ни один не отсутствует, таким образом, самый маленький целый, который не находится в массиве 4 .
3. Отсортированная решетка
Теперь давайте посмотрим, как найти самый маленький недостающий номер в отсортированного массива. В отсортированного массива, самый маленький недостающий целый будет первый индекс, который не держит себя в качестве значения.
Рассмотрим следующий отсортированную массив: (0, 1, 3, 4, 6, 7) . Теперь давайте посмотрим, какое значение соответствует какому индексу:
Index: 0 1 2 3 4 5 Value: 0 1 3 4 6 7
Как мы видим, индекс стоимости не держит интегратор 2 , поэтому 2 является самым маленьким недостающим целым в массиве.
Как насчет реализации этого алгоритма на Java? Давайте сначала создадим класс Самый маленькийМиссингПозитивИнтегер методом поискInSortedArray () :
public class SmallestMissingPositiveInteger { public static int searchInSortedArray(int[] input) { // ... } }
Теперь мы можем итерировать над массивом и поиск первого индекса, который не содержит себя в качестве значения и вернуть его в результате:
for (int i = 0; i < input.length; i++) { if (i != input[i]) { return i; } }
Наконец, если мы заверим цикл, не найдя недостающий элемент, мы должны вернуть следующий целый ряд, который является длиной массива , как мы начинаем с индексного 0 :
return input.length;
Давайте проверим, что все это работает, как ожидалось. Представьте себе массив целых 0 5 , с номером 3 недостающий:
int[] input = new int[] {0, 1, 2, 4, 5};
Тогда, если мы ищем первый пропавший integer, 3 должны быть возвращены:
int result = SmallestMissingPositiveInteger.searchInSortedArray(input); assertThat(result).isEqualTo(3);
Но, если мы ищем недостающий номер в массиве без каких-либо пропавших без вести целых:
int[] input = new int[] {0, 1, 2, 3, 4, 5};
Мы обнаружим, что первый пропавший integer 6 , которая является длина массива:
int result = SmallestMissingPositiveInteger.searchInSortedArray(input); assertThat(result).isEqualTo(input.length);
Далее мы увидим, как обрабатывать несортированные массивы.
4. Несортированный массив
Итак, как насчет поиска самых маленьких пропавших без вести целых в несортированном массиве? Есть несколько решений. Первый из них заключается в том, чтобы просто сортировать массив, а затем повторно использовать наш предыдущий алгоритм. Другой подход заключается в использовании другого массива, чтобы пометить целые ряды, которые присутствуют, а затем пройти этот массив, чтобы найти первый отсутствует.
4.1. Сортировка массива сначала
Начнем с первого решения и создадим новую поискInUnsortedArraySortingFirst () метод.
Итак, мы будем повторно использовать наш алгоритм, но сначала нам нужно сортировать наш входной массив. Для этого мы будем использовать Arrays.sort () :
Arrays.sort(input);
Этот метод сортирует свой вход в соответствии с его естественным порядком. Для интеграторов это означает от самого маленького к величайшему. Более подробная информация об алгоритмах сортировки содержится в нашей статье о сортировке массивов на Java.
После этого мы можем вызвать наш алгоритм с отсортированным вводом:
return searchInSortedArray(input);
Вот и все, теперь мы можем проверить, что все работает, как ожидалось. Давайте представим себе следующий массив с несортированной целых и недостающих номеров 1 и 3 :
int[] input = new int[] {4, 2, 0, 5};
Как 1 является самым маленьким недостающим integer, мы ожидаем, что это будет результатом вызова нашего метода:
int result = SmallestMissingPositiveInteger.searchInUnsortedArraySortingFirst(input); assertThat(result).isEqualTo(1);
Теперь давайте попробуем его на массиве без отсутствуют номера:
int[] input = new int[] {4, 5, 1, 3, 0, 2}; int result = SmallestMissingPositiveInteger.searchInUnsortedArraySortingFirst(input); assertThat(result).isEqualTo(input.length);
Вот и все, алгоритм возвращается 6 , то есть длина массива.
4.2. Использование Boolean Array
Другая возможность заключается в использовании другого массива – с той же длиной, что и входной массив – который булеан значения, сообщающие, был ли в массиве ввода найден целый ряд, соответствующий индексу, или нет.
Во-первых, давайте создадим третий метод, поискInUnsortedArrayBooleanArray () .
После этого давайте создадим массив булеан, флаги , и для каждого целого ряда входного массива, который соответствует индексу булеан массива, мы устанавливаем соответствующее значение для истинное :
boolean[] flags = new boolean[input.length]; for (int number : input) { if (number < flags.length) { flags[number] = true; } }
Теперь, наши флаги массив держит истинное для каждого целого ряда, присутствуют в входе массива, и ложные иначе. Тогда мы можем итерировать над флаги массив и вернуть первый индекс холдинга ложные . Если их нет, мы возвращаем длину массива:
for (int i = 0; i < flags.length; i++) { if (!flags[i]) { return i; } } return flags.length;
Опять же, давайте попробуем этот алгоритм с нашими примерами. Сначала мы повторно использовать массив отсутствует 1 и 3 :
int[] input = new int[] {4, 2, 0, 5};
Затем, при поиске самых маленьких пропавших без вести integer с нашим новым алгоритмом, ответ по-прежнему 1 :
int result = SmallestMissingPositiveInteger.searchInUnsortedArrayBooleanArray(input); assertThat(result).isEqualTo(1);
И для полного массива, ответ не меняется либо и по-прежнему 6 :
int[] input = new int[] {4, 5, 1, 3, 0, 2}; int result = SmallestMissingPositiveInteger.searchInUnsortedArrayBooleanArray(input); assertThat(result).isEqualTo(input.length);
5. Сложности
Теперь, когда мы рассмотрели алгоритмы, давайте поговорим об их сложностях, используя Big O нотации .
5.1. Отсортированная решетка
Начнем с первого алгоритма, по которому ввод уже отсортирован. В этом случае наихудшим сценарием является не поиск отсутствуюшего целых ряда и, следовательно, прохождение всего массива. Это означает у нас есть линейная сложность , что отмечается О(н) , учитывая n является длина нашего вклада.
5.2. Несортированный массив с алгоритмом сортировки
Теперь рассмотрим наш второй алгоритм. В этом случае входной массив не сортирован, и мы сортим его перед применением первого алгоритма. Вот, сложность будет наибольшей между механизмом сортировки и алгоритмом, который .
По данный вопрос о Java 11 Arrays.sort () метод использует двухъявимную алгоритм быстрого сортировки сортировать массивы. Сложность этого алгоритма сортировки, как правило, O (n log(n)) , хотя это может ухудшиться до O(n2) . Это означает, сложность нашего алгоритма будет O (n log(n)) в целом, а также может деградировать до квадратной сложности O(n2) .
Это для сложности времени, но давайте не будем забывать о пространстве. Хотя алгоритм поиска не занимает дополнительное пространство, алгоритм сортировки делает. Алгоритм быстрого сортировки занимает до O (журнал(n)) пространство для выполнения. Это то, что мы можем рассмотреть при выборе алгоритма для больших массивов.
5.3. Несортированный массив с Boolean Array
Наконец, давайте посмотрим, как работает наш третий и последний алгоритм. Для этого мы не сортим входной массив, что означает мы не страдаем от сложности сортировки . На самом деле, мы проходим только два массива, оба одного размера. Это означает, наша сложность времени должна быть O(2n) , который упрощается до О(н) . Это лучше, чем предыдущий алгоритм.
Но, когда дело доходит до сложности пространства, мы создаем второй массив такого же размера, как вход. Это означает, у нас О(н) сложность пространства , что хуже предыдущего алгоритма.
Зная все это, это до нас, чтобы выбрать алгоритм, который наилучшим образом соответствует нашим потребностям, в зависимости от условий, в которых он будет использоваться.
6. Заключение
В этой статье мы рассмотрели алгоритмы поиска малейшего недостающего положительного целых ряда в массиве. Мы видели, как достичь этого в отсортированном массиве, а также в несортированном массиве. Мы также обсудили время и пространство сложности различных алгоритмов, что позволяет нам выбирать один мудро в соответствии с нашими потребностями.
Как обычно, полные примеры кода, показанные в этой статье, доступны более на GitHub .