Найти дубликат в массиве целых чисел
это был вопрос.
мне дали массив 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
