Если у вас есть TreeSet
или любой NavigableSet
в целом, вы можете использовать вариант Двоичный поиск , чтобы найти первое пропущенное значение:
static Integer firstMissing(NavigableSet<Integer> set) {
if(set.size() <= 1) return null;
Integer first = set.first(), last = set.last();
if(set.size() == last - first + 1) return null; // no gaps at all
while(true) {
int middle = (first+last)>>>1;
NavigableSet<Integer> sub = set.headSet(middle, false);
if(sub.size() < middle - first) {// gap before middle
set = sub;
last = sub.last();
}
else {
set = set.tailSet(middle, true);
first = set.first();
if(first != middle) return middle;
}
}
}
будет называться как
NavigableSet<Integer> set = new TreeSet<>(Arrays.asList(1, 2, 3, 4, 6, 7, 8, 9, 10));
System.out.println(firstMissing(set));
Во-первых, поскольку Set
не содержит дубликатов, мы можем использовать минимальное и максимальное число, чтобы вычислить, какой размер должен иметь набор последовательных чисел. Если набор имеет такой размер, мы знаем, что пробелов нет, и можем вернуться немедленно. В противном случае мы вычисляем среднее число и разбиваем множество на две части. Для первой половины набора мы можем выполнить тот же тест, чтобы определить, есть ли в нем разрыв, и продолжить работу только с той половиной набора, чтобы найти первый разрыв. В противном случае мы берем вторую половину, уже зная, что должен быть разрыв. Поиск заканчивается, когда этот набор не содержит нашего среднего числа.
Если у вас есть произвольный Set
, без гарантированного порядка, лучшего способа сделать это не существует, так как каждый подход работает хорошо для одного ввода, но хуже для другого.
Вы можете просто скопировать набор в TreeSet
, используя new TreeSet<>(set)
, и использовать метод, описанный выше
Вы можете переключаться между диапазонами номеров, чтобы проверить наличие, затем отсутствие чисел
static Integer firstMissing(Set<Integer> set) {
if(set.size() <= 1) return null;
Integer firstPresent = null, firstAbsent = null;
for(int i = Integer.MIN_VALUE; firstPresent == null; i++)
if(set.contains(i)) firstPresent = i;
for(int i = firstPresent+1; firstAbsent == null; i++)
if(!set.contains(i)) firstAbsent = i;
return firstAbsent-firstPresent == set.size()? null: firstAbsent;
}
Условия цикла используют предварительное тестирование, которое гарантирует, что в наборе есть как минимум два числа.
Очевидная проблема - большой диапазон номеров, мы должны исследовать. Если мы знаем, что все числа положительные, мы можем заменить Integer.MIN_VALUE
на ноль.
вы можете перебирать содержимое набора, чтобы записывать все встречающиеся значения в доступную для поиска структуру данных. Это похоже на метод копирования, описанный выше, но, например, если все числа положительны, вы можете использовать следующий тест:
static Integer firstMissing(Set<Integer> set) {
if(set.size() <= 1) return null;
BitSet bs = new BitSet();
set.forEach(bs::set);
int firstPresent = bs.nextSetBit(0), firstAbsent = bs.nextClearBit(firstPresent);
return firstAbsent-firstPresent == set.size()? null: firstAbsent;
}
Он работает намного лучше, чем TreeSet
, если пропущено только несколько чисел или их нет вообще, но гораздо хуже, если значения действительно редки.