Алгоритм: эффективный способ удаления повторяющихся целых чисел из массива
Я получил эту проблему из интервью с Microsoft.
Учитывая массив случайных целых чисел, написать алгоритм в C, который удаляет дублировать номера и возвращать уникальные номера в оригинале массив.
Например, вход: {4, 8, 4, 1, 1, 2, 9}
Выход: {4, 8, 1, 2, 9, ?, ?}
Одно из предостережений заключается в том, что ожидаемый алгоритм не должен требовать, чтобы массив сначала сортировался. И когда элемент был удален, следующие элементы также должны быть сдвинуты вперед. Во всяком случае, значение элементов в хвосте массива, где элементы были сдвинуты вперед, незначительно.
Обновление: Результат должен быть возвращен в исходном массиве, а вспомогательная структура данных (например, hashtable) не должна использоваться. Однако, я думаю, сохранение заказа не требуется.
Update2: Для тех, кто задается вопросом, почему эти непрактичные ограничения, это был вопрос интервью, и все эти ограничения обсуждаются во время процесса мышления, чтобы увидеть, как я могу придумать разные идеи.
Ответы
Ответ 1
Как насчет:
void rmdup(int *array, int length)
{
int *current , *end = array + length - 1;
for ( current = array + 1; array < end; array++, current = array + 1 )
{
while ( current <= end )
{
if ( *current == *array )
{
*current = *end--;
}
else
{
current++;
}
}
}
}
Должно быть O (n ^ 2) или меньше.
Ответ 2
Решение, предложенное моей подругой, является разновидностью сортировки слияния. Единственная модификация заключается в том, что во время шага слияния просто игнорируйте дублирующиеся значения. Это решение было бы также O (n log n). В этом подходе удаление сортировки/дублирования объединяется вместе. Однако я не уверен, что это имеет значение.
Ответ 3
Я разместил это раньше, но я воспроизведу его здесь, потому что это довольно круто. Он использует хэширование, создавая что-то вроде хеш-набора на месте. Это гарантировано, что O (1) в подмышечной области (рекурсия - это хвостовой вызов) и, как правило, O (N). Алгоритм выглядит следующим образом:
- Возьмите первый элемент массива, это будет дозор.
- Измените порядок остальной части массива, насколько это возможно, чтобы каждый элемент находился в позиции, соответствующей его хешу. По завершении этого действия будут обнаружены дубликаты. Установите их равными дозорному.
- Переместите все элементы, для которых индекс равен хешу, к началу массива.
- Переместить все элементы, которые равны дозорному, кроме первого элемента массива, в конец массива.
- То, что осталось между правильно хэшированными элементами и повторяющимися элементами, будет элементами, которые нельзя было бы помещать в индекс, соответствующий их хешу, из-за столкновения. Учтите, чтобы иметь дело с этими элементами.
Это может быть показано как O (N), если в хешировании не существует патологического сценария: даже если дубликатов нет, примерно 2/3 элементов будут устранены при каждой рекурсии. Каждый уровень рекурсии равен O (n), где малый n - количество оставшихся элементов. Единственная проблема заключается в том, что на практике он медленнее, чем быстрый, когда имеется несколько дубликатов, т.е. Много столкновений. Однако, когда есть огромное количество дубликатов, это удивительно быстро.
Изменить: В текущих реализациях D hash_t - 32 бита. Все об этом алгоритме предполагает, что будет очень мало, если таковые имеются, хеш-коллизий в полном 32-битном пространстве. Однако столкновения могут часто возникать в пространстве модулей. Однако это предположение, по всей вероятности, будет справедливым для любого набора данных с разумным размером. Если ключ меньше или равен 32 битам, это может быть его собственный хеш, что означает, что столкновение в полном 32-битном пространстве невозможно. Если он больше, вы просто не можете разместить их достаточно в 32-битном адресном пространстве памяти, чтобы это было проблемой. Я предполагаю, что hash_t будет увеличено до 64 бит в 64-битных реализациях D, где наборы данных могут быть больше. Более того, если это когда-либо окажется проблемой, можно изменить хэш-функцию на каждом уровне рекурсии.
Здесь реализована реализация на языке программирования D:
void uniqueInPlace(T)(ref T[] dataIn) {
uniqueInPlaceImpl(dataIn, 0);
}
void uniqueInPlaceImpl(T)(ref T[] dataIn, size_t start) {
if(dataIn.length - start < 2)
return;
invariant T sentinel = dataIn[start];
T[] data = dataIn[start + 1..$];
static hash_t getHash(T elem) {
static if(is(T == uint) || is(T == int)) {
return cast(hash_t) elem;
} else static if(__traits(compiles, elem.toHash)) {
return elem.toHash;
} else {
static auto ti = typeid(typeof(elem));
return ti.getHash(&elem);
}
}
for(size_t index = 0; index < data.length;) {
if(data[index] == sentinel) {
index++;
continue;
}
auto hash = getHash(data[index]) % data.length;
if(index == hash) {
index++;
continue;
}
if(data[index] == data[hash]) {
data[index] = sentinel;
index++;
continue;
}
if(data[hash] == sentinel) {
swap(data[hash], data[index]);
index++;
continue;
}
auto hashHash = getHash(data[hash]) % data.length;
if(hashHash != hash) {
swap(data[index], data[hash]);
if(hash < index)
index++;
} else {
index++;
}
}
size_t swapPos = 0;
foreach(i; 0..data.length) {
if(data[i] != sentinel && i == getHash(data[i]) % data.length) {
swap(data[i], data[swapPos++]);
}
}
size_t sentinelPos = data.length;
for(size_t i = swapPos; i < sentinelPos;) {
if(data[i] == sentinel) {
swap(data[i], data[--sentinelPos]);
} else {
i++;
}
}
dataIn = dataIn[0..sentinelPos + start + 1];
uniqueInPlaceImpl(dataIn, start + swapPos + 1);
}
Ответ 4
Если вы ищете превосходную O-нотацию, сортировка массива с помощью сортировки O (n log n), то выполнение обхода O (n) может быть лучшим. Без сортировки вы смотрите на O (n ^ 2).
Изменить: если вы просто выполняете целые числа, то вы также можете сделать сортировку radix, чтобы получить O (n).
Ответ 5
Еще одна эффективная реализация
int i, j;
/* new length of modified array */
int NewLength = 1;
for(i=1; i< Length; i++){
for(j=0; j< NewLength ; j++)
{
if(array[i] == array[j])
break;
}
/* if none of the values in index[0..j] of array is not same as array[i],
then copy the current value to corresponding new position in array */
if (j==NewLength )
array[NewLength++] = array[i];
}
В этой реализации нет необходимости сортировать массив.
Также, если найден дублирующий элемент, нет необходимости переводить все элементы после этого на одну позицию.
Результатом этого кода является массив [] с размером NewLength
Здесь мы начинаем с 2-го элемента в массиве и сравниваем его со всеми элементами в массиве до этого массива.
У нас есть дополнительная индексная переменная 'NewLength' для изменения входного массива.
Параметр newLength инициализируется равным 0.
Элемент в массиве [1] будет сравниваться с массивом [0].
Если они разные, тогда значение в массиве [NewLength] будет изменено с помощью массива [1] и увеличится NewLength.
Если они одинаковы, NewLength не будет изменен.
Итак, если у нас есть массив [1 2 1 3 1],
затем
В первом проходе цикла 'j' массив [1] (2) будет сравниваться с array0, затем 2 будет записан в массив [NewLength] = array [1]
поэтому массив будет [1 2], так как NewLength = 2
Во втором проходе цикла 'j' массив [2] (1) будет сравниваться с array0 и array1. Здесь, поскольку массив [2] (1) и array0 - это тот же цикл, он будет разбит здесь.
поэтому массив будет [1 2], так как NewLength = 2
и т.д.
Ответ 6
1. Используя O (1) дополнительное пространство, в O (n log n) время
Это возможно, например:
- сначала выполните сортировку O (n log n) на месте
- затем пройдите через список один раз, введя первый экземпляр каждого из них в начало списка
Я считаю, что партнер ejel верен, что лучший способ сделать это - это сортировка слияния на месте с упрощенным шагом слияния, и это, вероятно, является целью вопроса, если бы вы были, например. написав новую библиотечную функцию, чтобы сделать это максимально эффективно, без возможности улучшить ввод данных, и были бы случаи, когда было бы полезно сделать это без хеш-таблицы, в зависимости от вида входных данных. Но я на самом деле не проверял это.
2. Использование O (лотов) дополнительного пространства, в O (n) времени
- объявить массив с нулевым значением, достаточным для хранения всех целых чисел
- пройдите через массив один раз
- установите для соответствующего элемента массива значение 1 для каждого целого.
- Если это уже было 1, пропустите это целое число.
Это работает только при наличии нескольких сомнительных допущений:
- Это возможно для нулевой памяти дешево, или размер ints мал по сравнению с количеством из них
- вы с удовольствием спросите свою ОС о 256 памяти sizepof (int)
- и он будет кэшировать его для вас действительно эффективно, если это гигантский
Это плохой ответ, но если у вас есть LOTS входных элементов, но это все 8-битные целые числа (или, может быть, даже 16-битные целые числа), это может быть лучшим способом.
3. O (немного) - дополнительное пространство, O (n) - время
Как # 2, но используйте хеш-таблицу.
4. Четкий способ
Если количество элементов невелико, запись соответствующего алгоритма не является полезной, если другой код быстрее писать и быстрее читать.
Eg. Пройдите через массив для каждого уникального элемента (т.е. Первый элемент, второй элемент (дубликаты первого из них были удалены) и т.д.), Удалив все одинаковые элементы. O (1) дополнительное пространство, O (n ^ 2) время.
Eg. Используйте функции библиотеки, которые это делают. эффективность зависит от того, что вы легко доступны.
Ответ 7
Ну, это простая реализация довольно проста. Пройдите через все элементы, проверьте, есть ли дубликаты в остальных и сдвиньте остальные на них.
Это ужасно неэффективно, и вы можете ускорить его с помощью вспомогательного массива для вывода или сортировки/бинарных деревьев, но это, похоже, не разрешено.
Ответ 8
Вы можете сделать это одним движением, если вы готовы пожертвовать памятью. Вы можете просто подсчитать, видели ли вы целое число или нет в хеш-ассоциативном массиве. Если вы уже видели число, удалите его по мере продвижения или, еще лучше, переместите числа, которые вы не видели в новый массив, избегая любых изменений в исходном массиве.
В Perl:
foreach $i (@myary) {
if(!defined $seen{$i}) {
$seen{$i} = 1;
push @newary, $i;
}
}
Ответ 9
Если вам разрешено использовать С++, вызов std::sort
, за которым следует вызов std::unique
, даст вам ответ. Сложность времени - O (N log N) для сортировки и O (N) для единственного обхода.
И если С++ отключен от таблицы, нет ничего, что заставило бы эти же алгоритмы записываться в C.
Ответ 10
Возвращаемое значение функции должно быть числом уникальных элементов, и все они хранятся в передней части массива. Без этой дополнительной информации вы даже не узнаете, есть ли дубликаты.
Каждая итерация внешнего цикла обрабатывает один элемент массива. Если он уникален, он остается в передней части массива, и если он является дубликатом, он перезаписывается последним необработанным элементом в массиве. Это решение работает в O (n ^ 2) времени.
#include <stdio.h>
#include <stdlib.h>
size_t rmdup(int *arr, size_t len)
{
size_t prev = 0;
size_t curr = 1;
size_t last = len - 1;
while (curr <= last) {
for (prev = 0; prev < curr && arr[curr] != arr[prev]; ++prev);
if (prev == curr) {
++curr;
} else {
arr[curr] = arr[last];
--last;
}
}
return curr;
}
void print_array(int *arr, size_t len)
{
printf("{");
size_t curr = 0;
for (curr = 0; curr < len; ++curr) {
if (curr > 0) printf(", ");
printf("%d", arr[curr]);
}
printf("}");
}
int main()
{
int arr[] = {4, 8, 4, 1, 1, 2, 9};
printf("Before: ");
size_t len = sizeof (arr) / sizeof (arr[0]);
print_array(arr, len);
len = rmdup(arr, len);
printf("\nAfter: ");
print_array(arr, len);
printf("\n");
return 0;
}
Ответ 11
Очевидно, что массив должен быть "пройден" справа налево, чтобы избежать ненужного копирования значений взад и вперед.
Если у вас есть неограниченная память, вы можете выделить бит-массив для sizeof(type-of-element-in-array) / 8
байтов, чтобы каждый бит означал, что вы уже встретили соответствующее значение или нет.
Если вы этого не сделаете, я не могу придумать ничего лучше, чем пересечение массива и сравнение каждого значения со значениями, которые следуют за ним, а затем, если будет найден дубликат, полностью удалить эти значения. Это где-то вблизи O (n ^ 2) (или O ((n ^ 2-n)/2)).
В IBM есть статья по своему близкому вопросу.
Ответ 12
Посмотрим:
- O (N) перейти, чтобы найти min/max allocate
- бит-массив для найденного
- O (N) пропускает дубликаты для завершения.
Ответ 13
Вот версия Java.
int[] removeDuplicate(int[] input){
int arrayLen = input.length;
for(int i=0;i<arrayLen;i++){
for(int j = i+1; j< arrayLen ; j++){
if(((input[i]^input[j]) == 0)){
input[j] = 0;
}
if((input[j]==0) && j<arrayLen-1){
input[j] = input[j+1];
input[j+1] = 0;
}
}
}
return input;
}
Ответ 14
В Java я бы решил это так. Не знаю, как записать это в C.
int length = array.length;
for (int i = 0; i < length; i++)
{
for (int j = i + 1; j < length; j++)
{
if (array[i] == array[j])
{
int k, j;
for (k = j + 1, l = j; k < length; k++, l++)
{
if (array[k] != array[i])
{
array[l] = array[k];
}
else
{
l--;
}
}
length = l;
}
}
}
Ответ 15
Это можно сделать за один проход с помощью алгоритма O (N log N) и без дополнительного хранилища.
Перейдите от элемента a[1]
к a[N]
. На каждом этапе i
все элементы слева от a[i]
содержат отсортированную кучу элементов a[0]
через a[j]
. Между тем, второй индекс j
, первоначально 0, отслеживает размер кучи.
Изучите a[i]
и вставьте его в кучу, которая теперь занимает элементы a[0]
до a[j+1]
. Когда элемент вставлен, если встречается повторяющийся элемент a[k]
с одинаковым значением, не вставляйте a[i]
в кучу (т.е. Отбрасывайте его); иначе вставьте его в кучу, которая теперь растет на один элемент и теперь содержит от a[0]
до a[j+1]
и увеличивает j
.
Продолжайте таким образом, увеличивая i
до тех пор, пока все элементы массива не будут рассмотрены и не вставлены в кучу, которая заканчивается тем, что занимает от a[0]
до a[j]
. j
- это индекс последнего элемента кучи, а куча содержит только уникальные значения элементов.
int algorithm(int[] a, int n)
{
int i, j;
for (j = 0, i = 1; i < n; i++)
{
// Insert a[i] into the heap a[0...j]
if (heapInsert(a, j, a[i]))
j++;
}
return j;
}
bool heapInsert(a[], int n, int val)
{
// Insert val into heap a[0...n]
...code omitted for brevity...
if (duplicate element a[k] == val)
return false;
a[k] = val;
return true;
}
Глядя на пример, это не совсем то, о чем просили, поскольку результирующий массив сохраняет исходный порядок элементов. Но если это требование ослаблено, алгоритм выше должен сделать трюк.
Ответ 16
Как насчет следующего?
int* temp = malloc(sizeof(int)*len);
int count = 0;
int x =0;
int y =0;
for(x=0;x<len;x++)
{
for(y=0;y<count;y++)
{
if(*(temp+y)==*(array+x))
{
break;
}
}
if(y==count)
{
*(temp+count) = *(array+x);
count++;
}
}
memcpy(array, temp, sizeof(int)*len);
Я пытаюсь объявить массив temp и поместить элементы в это, прежде чем копировать все обратно в исходный массив.
Ответ 17
После обзора проблемы, вот мой метод delphi, который может помочь
var
A: Array of Integer;
I,J,C,K, P: Integer;
begin
C:=10;
SetLength(A,10);
A[0]:=1; A[1]:=4; A[2]:=2; A[3]:=6; A[4]:=3; A[5]:=4;
A[6]:=3; A[7]:=4; A[8]:=2; A[9]:=5;
for I := 0 to C-1 do
begin
for J := I+1 to C-1 do
if A[I]=A[J] then
begin
for K := C-1 Downto J do
if A[J]<>A[k] then
begin
P:=A[K];
A[K]:=0;
A[J]:=P;
C:=K;
break;
end
else
begin
A[K]:=0;
C:=K;
end;
end;
end;
//tructate array
setlength(A,C);
end;
Ответ 18
Следующий пример должен решить вашу проблему:
def check_dump(x):
if not x in t:
t.append(x)
return True
t=[]
output = filter(check_dump, input)
print(output)
True
Ответ 19
Вот мое решение.
///// find duplicates in an array and remove them
void unique(int* input, int n)
{
merge_sort(input, 0, n) ;
int prev = 0 ;
for(int i = 1 ; i < n ; i++)
{
if(input[i] != input[prev])
if(prev < i-1)
input[prev++] = input[i] ;
}
}
Ответ 20
import java.util.ArrayList;
public class C {
public static void main(String[] args) {
int arr[] = {2,5,5,5,9,11,11,23,34,34,34,45,45};
ArrayList<Integer> arr1 = new ArrayList<Integer>();
for(int i=0;i<arr.length-1;i++){
if(arr[i] == arr[i+1]){
arr[i] = 99999;
}
}
for(int i=0;i<arr.length;i++){
if(arr[i] != 99999){
arr1.add(arr[i]);
}
}
System.out.println(arr1);
}
}
Ответ 21
Это наивное (N * (N-1)/2) решение. Он использует постоянное дополнительное пространство и сохраняет исходный порядок. Это похоже на решение @Byju, но не использует блоки if(){}
. Он также позволяет избежать копирования элемента на себя.
#include <stdio.h>
#include <stdlib.h>
int numbers[] = {4, 8, 4, 1, 1, 2, 9};
#define COUNT (sizeof numbers / sizeof numbers[0])
size_t undup_it(int array[], size_t len)
{
size_t src,dst;
/* an array of size=1 cannot contain duplicate values */
if (len <2) return len;
/* an array of size>1 will cannot at least one unique value */
for (src=dst=1; src < len; src++) {
size_t cur;
for (cur=0; cur < dst; cur++ ) {
if (array[cur] == array[src]) break;
}
if (cur != dst) continue; /* found a duplicate */
/* array[src] must be new: add it to the list of non-duplicates */
if (dst < src) array[dst] = array[src]; /* avoid copy-to-self */
dst++;
}
return dst; /* number of valid alements in new array */
}
void print_it(int array[], size_t len)
{
size_t idx;
for (idx=0; idx < len; idx++) {
printf("%c %d", (idx) ? ',' :'{' , array[idx] );
}
printf("}\n" );
}
int main(void) {
size_t cnt = COUNT;
printf("Before undup:" );
print_it(numbers, cnt);
cnt = undup_it(numbers,cnt);
printf("After undup:" );
print_it(numbers, cnt);
return 0;
}
Ответ 22
Это можно сделать за один проход в O (N) раз в количестве целых чисел на входе
список и O (N) хранилище в количестве уникальных целых чисел.
Пройдите по списку спереди назад, с двумя указателями "dst" и
"src" инициализируется первым элементом. Начните с пустой хеш-таблицы
из числа "целых чисел". Если целое число в src отсутствует в хэше,
записать его в слот на dst и приращение dst. Добавьте целое число в src
к хешу, затем приращение src. Повторяйте до тех пор, пока src не завершит
список ввода.
Ответ 23
Вставьте все элементы в binary tree the disregards duplicates
- O(nlog(n))
. Затем извлеките все из них в массив, выполнив обход - O(n)
. Я предполагаю, что вам не нужно сохранять порядок.
Ответ 24
Используйте фильтр цветения для хеширования. Это значительно сократит объем памяти.
Ответ 25
Создайте BinarySearchTree
, у которого есть сложность O (n).
Ответ 26
В JAVA,
Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};
String value ="";
for(Integer i:arrayInteger)
{
if(!value.contains(Integer.toString(i))){
value +=Integer.toString(i)+",";
}
}
String[] arraySplitToString = value.split(",");
Integer[] arrayIntResult = new Integer[arraySplitToString.length];
for(int i = 0 ; i < arraySplitToString.length ; i++){
arrayIntResult[i] = Integer.parseInt(arraySplitToString[i]);
}
Выход:
{1, 2, 3, 4, 6, 7, 8, 9, 10}
надеюсь, что это поможет
Ответ 27
Сначала вы должны создать массив check[n]
, где n - количество элементов массива, которое вы хотите сделать без дубликатов, и установить значение каждого элемента (массива проверки) равным 1. Использование a для цикл пересекает массив с дубликатами, скажем, его имя arr
, а в for-loop пишите это:
{
if (check[arr[i]] != 1) {
arr[i] = 0;
}
else {
check[arr[i]] = 0;
}
}
При этом вы устанавливаете каждый дубликат равным нулю. Поэтому остается только пройти массив arr
и напечатать все, что не равно нулю. Порядок остается и он принимает линейное время (3 * n).
Ответ 28
Учитывая массив из n элементов, напишите алгоритм для удаления всех дубликатов из массива за время O (nlogn)
Algorithm delete_duplicates (a[1....n])
//Remove duplicates from the given array
//input parameters :a[1:n], an array of n elements.
{
temp[1:n]; //an array of n elements.
temp[i]=a[i];for i=1 to n
temp[i].value=a[i]
temp[i].key=i
//based on 'value' sort the array temp.
//based on 'value' delete duplicate elements from temp.
//based on 'key' sort the array temp.//construct an array p using temp.
p[i]=temp[i]value
return p.
В других элементах сохраняется в выходном массиве с использованием "ключа". Рассмотрим, что ключ имеет длину O (n), время, затраченное на выполнение сортировки по ключу, и значение O (nlogn). Таким образом, время, затраченное на удаление всех дубликатов из массива, - O (nlogn).
Ответ 29
это то, что у меня есть, хотя оно меняет порядок, который мы можем сортировать по восходящему или нисходящему, чтобы исправить его.
#include <stdio.h>
int main(void){
int x,n,myvar=0;
printf("Enter a number: \t");
scanf("%d",&n);
int arr[n],changedarr[n];
for(x=0;x<n;x++){
printf("Enter a number for array[%d]: ",x);
scanf("%d",&arr[x]);
}
printf("\nOriginal Number in an array\n");
for(x=0;x<n;x++){
printf("%d\t",arr[x]);
}
int i=0,j=0;
// printf("i\tj\tarr\tchanged\n");
for (int i = 0; i < n; i++)
{
// printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
for (int j = 0; j <n; j++)
{
if (i==j)
{
continue;
}
else if(arr[i]==arr[j]){
changedarr[j]=0;
}
else{
changedarr[i]=arr[i];
}
// printf("%d\t%d\t%d\t%d\n",i,j,arr[i],changedarr[i] );
}
myvar+=1;
}
// printf("\n\nmyvar=%d\n",myvar);
int count=0;
printf("\nThe unique items:\n");
for (int i = 0; i < myvar; i++)
{
if(changedarr[i]!=0){
count+=1;
printf("%d\t",changedarr[i]);
}
}
printf("\n");
}
Ответ 30
Было бы здорово, если бы у вас была хорошая DataStructure, которая могла бы быстро определить, содержит ли она целое число. Возможно, какое-то дерево.
DataStructure elementsSeen = new DataStructure();
int elementsRemoved = 0;
for(int i=0;i<array.Length;i++){
if(elementsSeen.Contains(array[i])
elementsRemoved++;
else
array[i-elementsRemoved] = array[i];
}
array.Length = array.Length - elementsRemoved;