Найти дубликат в массиве целых чисел

это был вопрос.

мне дали массив n+1 целые числа из диапазона [1,n]. Свойство массива заключается в том, что он имеет k (k>=1) дублей, и каждый дубликат может появиться более чем в два раза. Задача состояла в том, чтобы найти элемент массива, который встречается не один раз в наилучшей временной и пространственной сложности.

после значительной борьбы, я с гордостью придумал O(nlogn) решение O(1) пространство. Моей идеей было разделить диапазон [1,n-1] в две половины и определите, какая из двух половин содержит больше элементов из входного массива (я использовал принцип Pigeonhole). Алгоритм продолжается рекурсивно, пока не достигнет интервала [X,X] здесь X происходит дважды, и это дубликат.

собеседник был доволен, но потом он сказал мне, что существует O(n) решение проблемы с постоянным пространством. Он великодушно предложил несколько намеков (что-то связанное с перестановками?), но я понятия не имел, как пришел с таким решением. Если предположить, что он не лгал, может ли кто-нибудь предложить рекомендации? Я искал так и нашел несколько (более легких) вариантов этой проблемы, но не этот конкретный. Спасибо.

EDIT: чтобы сделать вещи еще более сложными, интервьюер упомянул, что входной массив не должен быть изменен.

4 ответов


  1. возьмите самый последний элемент (x).

  2. сохраните элемент в позиции x (y).

  3. если x == y вы нашли дубликат.

  4. заменить позицию x на x.

  5. назначьте 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) является первым номером цикла.

  1. нахождение цикла перестановок

    1. x = последний элемент
    2. X = элемент в позиции x
    3. повторите шаг 2. n раз (всего), это гарантирует, что мы вошли в цикл
  2. измерение длины цикла

    1. a = последний x сверху, b = последний x сверху, счетчик c = 0
    2. A = элемент в позиции a, b = элемент в позиции b, B = элемент в позиции b, C++ (таким образом, мы делаем 2 шага вперед с b и 1 шаг вперед в цикле с a)
    3. Если a == b длина цикла c, в противном случае продолжите с шагом 2.
  3. поиск точки входа в цикл

    1. x = последний элемент
    2. X = элемент в позиции x
    3. повторите шаг 2. c времен (в итого)
    4. y = Последний элемент
    5. Если x == y, то x-это решение (x сделал один полный цикл, и y как раз собирается войти в цикл)
    6. X = элемент в положении x, y = элемент в положении y
    7. повторите шаги 5. и 6. пока решение не было найдено.

3 основных шага-все O(n) и последовательные, поэтому общая сложность также O(n), а сложность пространства-O (1).

пример из сверху:

  1. X принимает следующие значения: 8 7 6 4 1 2 3 4 1 2

  2. a принимает следующие значения: 2 3 4 1 2
    Б принимает следующие значения: 2 4 2 4 2
    поэтому c = 4 (Да, есть 5 чисел, но c увеличивается только при выполнении шагов, а не изначально)

  3. 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

  1. вступаем цикла: 5 1 3 4 6 2 1 3

  2. измеряя длина цикла:
    ответ: 3 4 6 2 1 3
    Б: 3 6 1 4 2 3
    c = 5

  3. поиск точки входа:
    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".

  1. установите i в 0 и выполните следующий цикл:
    1. инкремент i до тех пор, пока элемент i не будет unflagged.
    2. установите j в i и выполните следующий цикл:
      1. установите j в вектор[j].
      2. если элемент в j помечен, j является дубликатом. Завершите оба цикла.
      3. отметьте пункт в j.
      4. если j != I, продолжайте внутренний цикл.
  2. пересеките вектор, устанавливающий каждый элемент до его абсолютного значения (т. е. unflag все, чтобы восстановить вектор).

  • Это зависит от того, какие инструменты вы(ваше приложение) может использовать. В настоящее время существует множество фреймворков/библиотек. Для exmaple в случае стандарта C++ вы можете использовать std:: map ,как упоминалось в maraca.

  • или если у вас есть время, вы можете сделать собственную реализацию двоичного дерева, но нужно иметь в виду, что вставка элементов отличается по сравнению с обычным массивом. В этом случае вы можете оптимизировать поиск дубликатов, насколько это возможно в вашем конкретном случае случай.

двоичное дерево expl. ссылка: https://www.wikiwand.com/en/Binary_tree