Найти дубликат в массиве целых чисел
это был вопрос.
мне дали массив n+1
целые числа из диапазона [1,n]
. Свойство массива заключается в том, что он имеет k (k>=1)
дублей, и каждый дубликат может появиться более чем в два раза. Задача состояла в том, чтобы найти элемент массива, который встречается не один раз в наилучшей временной и пространственной сложности.
после значительной борьбы, я с гордостью придумал O(nlogn)
решение O(1)
пространство. Моей идеей было разделить диапазон [1,n-1]
в две половины и определите, какая из двух половин содержит больше элементов из входного массива (я использовал принцип Pigeonhole). Алгоритм продолжается рекурсивно, пока не достигнет интервала [X,X]
здесь X
происходит дважды, и это дубликат.
собеседник был доволен, но потом он сказал мне, что существует O(n)
решение проблемы с постоянным пространством. Он великодушно предложил несколько намеков (что-то связанное с перестановками?), но я понятия не имел, как пришел с таким решением. Если предположить, что он не лгал, может ли кто-нибудь предложить рекомендации? Я искал так и нашел несколько (более легких) вариантов этой проблемы, но не этот конкретный. Спасибо.
EDIT: чтобы сделать вещи еще более сложными, интервьюер упомянул, что входной массив не должен быть изменен.
4 ответов
возьмите самый последний элемент (x).
сохраните элемент в позиции x (y).
если x == y вы нашли дубликат.
заменить позицию x на x.
назначьте x = y и перейдите к Шагу 2.
вы в основном сортируете массив, это возможно, потому что вы знаете, где должен быть вставлен элемент. O (1) дополнительное пространство и O (n) время сложность. Вы просто должны быть осторожны с индексами, для простоты я предположил, что первый индекс равен 1 здесь (не 0), поэтому мы не должны делать +1 или -1.
Edit: без изменения входного массива
этот алгоритм основан на идее, что мы должны найти точку входа цикла перестановки, тогда мы также нашли дубликат (опять же 1-массив для простоты):
пример:
2 3 4 1 5 4 6 7 8
запись: 8 7 6
цикл перестановки: 4 1 2 3
как мы видим, дубликат (4) является первым номером цикла.
-
нахождение цикла перестановок
- x = последний элемент
- X = элемент в позиции x
- повторите шаг 2. n раз (всего), это гарантирует, что мы вошли в цикл
-
измерение длины цикла
- a = последний x сверху, b = последний x сверху, счетчик c = 0
- A = элемент в позиции a, b = элемент в позиции b, B = элемент в позиции b, C++ (таким образом, мы делаем 2 шага вперед с b и 1 шаг вперед в цикле с a)
- Если a == b длина цикла c, в противном случае продолжите с шагом 2.
-
поиск точки входа в цикл
- x = последний элемент
- X = элемент в позиции x
- повторите шаг 2. c времен (в итого)
- y = Последний элемент
- Если x == y, то x-это решение (x сделал один полный цикл, и y как раз собирается войти в цикл)
- X = элемент в положении x, y = элемент в положении y
- повторите шаги 5. и 6. пока решение не было найдено.
3 основных шага-все O(n) и последовательные, поэтому общая сложность также O(n), а сложность пространства-O (1).
пример из сверху:
X принимает следующие значения: 8 7 6 4 1 2 3 4 1 2
a принимает следующие значения: 2 3 4 1 2
Б принимает следующие значения: 2 4 2 4 2
поэтому c = 4 (Да, есть 5 чисел, но c увеличивается только при выполнении шагов, а не изначально)X принимает следующие значения: 8 7 6 4 | 1 2 3 4
y принимает следующие значения:| 8 7 6 4
x == y == 4 в конце, и это решение!
Пример 2, как указано в комментариях:3 1 4 6 1 2 5
вступаем цикла: 5 1 3 4 6 2 1 3
измеряя длина цикла:
ответ: 3 4 6 2 1 3
Б: 3 6 1 4 2 3
c = 5поиск точки входа:
x: 5 1 3 4 6 | 2 1
y: / 5 1
x == y == 1-это решение
вот возможная реализация:
function checkDuplicate(arr) {
console.log(arr.join(", "));
let len = arr.length
,pos = 0
,done = 0
,cur = arr[0]
;
while (done < len) {
if (pos === cur) {
cur = arr[++pos];
} else {
pos = cur;
if (arr[pos] === cur) {
console.log(`> duplicate is ${cur}`);
return cur;
}
cur = arr[pos];
}
done++;
}
console.log("> no duplicate");
return -1;
}
for (t of [
[0, 1, 2, 3]
,[0, 1, 2, 1]
,[1, 0, 2, 3]
,[1, 1, 0, 2, 4]
]) checkDuplicate(t);
это в основном решение, предложенное @maraca (набирается слишком медленно!) Он имеет постоянные требования к пространству (для локальных переменных), но кроме этого использует только исходный массив для его хранения. Он должен быть!--1--> в худшем случае, потому что как только дубликат найден, процесс завершается.
Если вам разрешено без разрушения изменять входной вектор, то это довольно легко. Предположим, мы можем" пометить " элемент на входе, отрицая его (что, очевидно, обратимо). В этом случае мы можем действовать следующим образом:
Примечание: следующее предположим, что вектор индексируется, начиная с 1. Поскольку он, вероятно, индексируется, начиная с 0( на большинстве языков), вы можете реализовать "элемент флага в индексе i" с "отрицанием элемента в индексе i-1".
- установите i в 0 и выполните следующий цикл:
- инкремент i до тех пор, пока элемент i не будет unflagged.
- установите j в i и выполните следующий цикл:
- установите j в вектор[j].
- если элемент в j помечен, j является дубликатом. Завершите оба цикла.
- отметьте пункт в j.
- если j != I, продолжайте внутренний цикл.
- пересеките вектор, устанавливающий каждый элемент до его абсолютного значения (т. е. unflag все, чтобы восстановить вектор).
Это зависит от того, какие инструменты вы(ваше приложение) может использовать. В настоящее время существует множество фреймворков/библиотек. Для exmaple в случае стандарта C++ вы можете использовать std:: map ,как упоминалось в maraca.
или если у вас есть время, вы можете сделать собственную реализацию двоичного дерева, но нужно иметь в виду, что вставка элементов отличается по сравнению с обычным массивом. В этом случае вы можете оптимизировать поиск дубликатов, насколько это возможно в вашем конкретном случае случай.
двоичное дерево expl. ссылка: https://www.wikiwand.com/en/Binary_tree