it-swarm.com.ru

Переместить дубликаты в конец отсортированного массива

Мне задали этот вопрос в интервью. Есть отсортированный массив с дубликатами. Цель состоит в том, чтобы сначала возвратить массив с уникальными элементами и дублировать в конце, сохраняя порядок. Например, [1, 1, 2, 3, 4, 4, 5] должен стать [1, 2, 3, 4, 5, 1, 4]

Мне удалось решить вопрос с дополнительным пробелом (O (n) пробел) и линейным временем (O (n) время), но я не уверен, что это лучший ответ, в идеале не используя линейный пробел. 

Я искал stackoverflow и нашел похожие вопросы, но не совсем то же самое. Например, был вопрос сортировка массива и перемещение дубликатов в конец, но в моем случае массив уже отсортирован, и цель состоит в том, чтобы только перемещать дубликаты в конец. 

8
apadana

Если ваши значения находятся в ограниченном диапазоне, существует решение во O(n) времени и O(1) пространстве.

Определите максимальное значение в массиве. Получите некоторую константу C > arraymax, например, - C = 10 для вашего массива.

Сканирование массива, сжатие уникальных значений и подсчет дубликатов для каждого значения. Если значение V имеет дубликаты K>0, вместо значения введите V+C*K.

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

def dedup(lst):
    mx = max(lst) + 1
    dupcnt = 0
    delcnt = 0
    start = 0
    for i in range(1, len(lst) + 1):
        if i == len(lst) or (lst[i] != lst[start]):
            lst[start - delcnt] = lst[start] + dupcnt * mx
            delcnt += dupcnt
            start = i
            dupcnt = 0
        else:
            dupcnt += 1
    dupidx = len(lst) - delcnt
    for i in range(0, len(lst) - delcnt):
        dupcnt = lst[i] // mx
        if dupcnt:
           lst[i] %= mx
           for j in range(dupidx, dupidx+dupcnt):
              lst[j] = lst[i]
           dupidx += dupcnt
    return lst

print(dedup([1,2,2,2,3,4,4,5]))
>>> [1, 2, 3, 4, 5, 2, 2, 4]
4
MBo

С риском констатировать очевидное. , , Подход во времениO( n log n ) иO(1) дополнительного пространства:

  1. Просканируйте массив, чтобы найти первый элемент с каждым значением, и поменяйте местами этот элемент прямо в правильную позицию. (Например, когда вы достигаете четвертого отдельного значения, вы меняете первый элемент с этим значением на позицию # 4.)
    • Этот шаг требуетO( n ) времени иO(1) дополнительного пространства.
    • После этого шага массив состоит из всех уникальных элементов в правильном порядке, за которыми следуют все дубликаты в порядке мусора.
  2. Сортируйте дубликаты, используя heapsort .
    • Этот шаг требуетO( n log n ) времени иO(1) дополнительного места.
1
ruakh

Вам нужно иметь 2-3 указателя (индекса) . I: следующие уникальные элементы будут помещены в эту позицию J: указатель линейного обхода в списке

private static void fix(int[] nums) {
    int i = 0;
    int j = 0;
    while (j < nums.length) {
        int k;
        for (k = j + 1; k < nums.length && nums[k] == nums[j]; k++) {

        }

        int tmp = nums[i];
        nums[i] = nums[j];
        nums[j] = tmp;
        j = k;
        i++;
    }
}
0
Alireza Salimi

ОБНОВЛЕНИЕ: неверно истолковано ваши намерения, когда ваше беспокойство касалось космоса, это PHP версия «указателей». Поскольку он отсортирован, мы можем пройти цикл один раз, верно? Если бы не мы, мы, вероятно, испекли бы двойную сортировку в саму сортировку.

function findRepeating(&$arr)
{
    $size = count($arr);
    $previous = -99999;
    for ($i = 0; $i < $size; $i++) {
        if ($i>0)
            $previous = $arr[$i-1];

        if ($arr[$i] == $previous) {
            array_Push($arr,$arr[$i]); //Push to end
            unset($arr[$i]); //then remove current one
        }
    }
    var_dump($arr);
}

В основном мы просто берем текущий размер массива, и когда мы находим дубликаты, нажимаем на конец массива, немного увеличивая его размер, который компенсируется unset ().

array(7) {
  [0]=>
  string(1) "1"
  [2]=>
  string(1) "2"
  [3]=>
  string(1) "3"
  [4]=>
  string(1) "4"
  [6]=>
  string(1) "5"
  [7]=>
  string(1) "1"
  [8]=>
  string(1) "4"
}

На языке более низкого уровня вы можете просто перемещаться вокруг указателей, потому что вы знаете конечное значение, поэтому вы принимаете это к сведению и просто добавляете дубликаты после этого и добавляете к смещению по мере его продвижения. Полностью достижимый с массивами или без них, с массивами мы просто меняемся местами. Мой пример приведен в PHP, поэтому вместо перетасовки я просто расширяю массив и временно использую один дополнительный пробел. 

0
Mike Q

Не совсем понятно, как обрабатывать несколько дубликатов или что вы точно спрашиваете, но я предполагаю, что вы хотите убедиться, что пространство O(1) удовлетворяется, независимо от сложности времени, так вот что я попытаюсь ответить.

С массивами, O(1) пробел, O (N ^ 2) время:

Вы можете сделать это на месте, просто поменяв дубликаты элементов до конца. Вы можете найти дубликаты элементов, сохранив «текущий» указатель и просто проверив, что «следующий» элемент не совпадает с «текущим». Это O (N ^ 2) время в худшем случае. Пример:

[1,1,2,3,4,4,5] # "cur" is index 0 (element 1), and "next" is index 1 (element 1). Swap "next" to end.
[1,2,1,3,4,4,5] # swapping
[1,2,3,1,4,4,5] # swapping
...             # Tedious swapping
[1,2,3,4,4,5,1] # Done swapping. Increment "cur".
[1,2,3,4,4,5,1] # "cur" is index 1 (element 2), and "next" is index 2 (element 3). Increment "cur"
...             # Boring (no duplicates detected)
[1,2,3,4,4,5,1] # "cur" is index 3 (element 4), and "next" is index 4 (element 4). Swap "next" to end.
[1,2,3,4,5,4,1] # swapping
[1,2,3,4,5,1,4] # Done swapping. Increment "cur"
...             # No more duplicates
# Done

Кроме того, на практике тратить время на меньшее пространство обычно не стоит. Память дешева, но медленное время отклика может потерять пользователей, что дорого. Заметным исключением являются встроенные системы, в которых память может быть ограничена, а входы короткими (для небольших входов асимптотическое время выполнения не имеет значения).

Со связанными списками, O(1) пробел, O(N) время:

Если бы у вас был связанный список вместо массива, вы могли бы сделать это за O(n) время и O(1) пространство довольно легко. Связанные списки имеют преимущество перед массивами, когда вы вынуждены «сдвигать» элементы, поскольку они могут перемещать указатели вместо перемещения ВСЕХ элементов по позиции. Стратегия cur/next аналогична для связанных списков, как указано выше с массивом. Вот пример:

1->1->2->3->4->4->5 # "cur" is first element (value 1), and "next" is second element (value 1). Swap "next" to the end.

1
 \
1->2->3->4->4->5    # Move "cur"'s pointer to "next"'s next element.

1->2->3->4->4->5->1 # Set "next"'s pointer to null, set tails pointer to "next"

...                 # Boring stuff with no duplicates

1->2->3->4->4->5->1 # "cur" is fourth element (value 4), and "next" is fifth element (value 4). Swap fifth element to end.

         4
          \
1->2->3->4->5->1    # Move "cur"'s pointer to "next"'s next element.

1->2->3->4->5->1->4 # Set "next"'s pointer to null, set tails pointer to "next"

...                 # No more duplicates
# Done (hopefully it's clear moving and element to the end is O(1) instead of O(n))

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

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

Умные структуры данных и тупой код работают намного лучше, чем другие наоборот. --Eric S Raymond

0
Matt Messersmith