我在接受微软采访时遇到了这个问题。
给定一组随机整数, 在C中写一个删除的算法 重复的数字并返回原始的唯一数字 阵列。
E.g输入:{4, 8, 4, 1, 1, 2, 9}
输出:{4, 8, 1, 2, 9, ?, ?}
需要注意的是,预期的算法不应该首先对数组进行排序。当一个元素被移除后,以下元素也必须向前移动。无论如何,元素尾部元素向前移动的元素值可以忽略不计。
更新:必须在原始数组中返回结果,并且不应使用辅助数据结构(例如哈希表)。但是,我想没有必要保留订单。
Update2:对于那些不知道为什么会出现这些不切实际的约束的人来说,这是一个面试问题,在思考过程中会讨论所有这些约束,看看我如何能够提出不同的想法。
答案 0 :(得分:134)
我女朋友建议的解决方案是合并排序的变体。唯一的修改是在合并步骤中,只是忽略重复的值。这个解决方案也是O(n log n)。在这种方法中,排序/复制删除被组合在一起。但是,我不确定这是否会有所不同。
答案 1 :(得分:45)
之前我已经发布了这个,但我会在这里重现它,因为它非常酷。它使用散列,构建类似哈希集的东西。它保证在腋窝空间中是O(1)(递归是尾调用),并且通常是O(N)时间复杂度。算法如下:
这可以显示为O(N),在散列中没有提供病理场景:即使没有重复,在每次递归时也会消除大约2/3的元素。每个递归级别为O(n),其中小n是剩余元素的数量。唯一的问题是,在实践中,当重复很少时,即快速排序,即大量碰撞时,它的速度要慢。然而,当存在大量重复时,它的速度非常快。
编辑:在D的当前实现中,hash_t是32位。关于此算法的所有内容都假定在完整的32位空间中将存在非常少的哈希冲突(如果有的话)。然而,碰撞可能在模数空间中频繁发生。然而,对于任何合理大小的数据集,这种假设很可能都是正确的。如果密钥小于或等于32位,则它可以是它自己的散列,这意味着完全32位空间中的冲突是不可能的。如果它更大,你根本无法将它们放入32位内存地址空间,因为它是一个问题。我假设在D的64位实现中hash_t将增加到64位,其中数据集可以更大。此外,如果这确实是一个问题,可以在每个递归级别更改散列函数。
以下是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);
}
答案 2 :(得分:20)
一个更有效的实施
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];
}
在此实现中,不需要对数组进行排序。 此外,如果找到重复元素,则不需要在此之后将所有元素移位一个位置。
此代码的输出是array [],大小为NewLength
这里我们从数组中的第二个elemt开始,并将它与阵列中的所有元素进行比较,直到这个数组。 我们持有一个额外的索引变量'NewLength'来修改输入数组。 NewLength变量初始化为0。
数组[1]中的元素将与数组[0]进行比较。 如果它们不同,那么数组[NewLength]中的值将被数组[1]修改并增加NewLength。 如果它们相同,则不会修改NewLength。
所以如果我们有一个数组[1 2 1 3 1], 然后
在'j'循环的第一次传递中,数组[1](2)将与array0进行比较,然后2将被写入数组[NewLength] = array [1] 因为NewLength = 2
,所以数组将是[1 2]在'j'循环的第二遍中,数组[2](1)将与array0和array1进行比较。这里因为数组[2](1)和array0是相同的循环将在这里打破。 因为NewLength = 2
,所以数组将是[1 2]等等
答案 3 :(得分:19)
如果您正在寻找优越的O符号,那么使用O(n log n)排序对数组进行排序,然后进行O(n)遍历可能是最佳路径。没有排序,你看O(n ^ 2)。
编辑:如果你只是做整数,那么你也可以做基数排序来得到O(n)。
答案 4 :(得分:18)
怎么样:
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)或更少。
答案 5 :(得分:10)
<强> 1。使用O(1)额外空格,在O(n log n)时间
这是可能的,例如:
我相信ejel的合作伙伴是正确的,最好的方法是使用简化的合并步骤进行就地合并排序,这可能是问题的意图,如果你是例如。编写一个新的库函数来尽可能高效地执行此操作而无法改进输入,并且在没有哈希表的情况下执行此操作会很有用,具体取决于输入的种类。但我实际上没有检查过这个。
<强> 2。在O(n)时间内使用O(大量)额外空间
这只有在有几个可疑的假设成立时才有效:
这是一个糟糕的答案,但如果你有很多输入元素,但它们都是8位整数(或者甚至可能是16位整数),那么它可能是最好的方法。
第3。 O(小) - 额外的空间,O(n) - 时间
作为#2,但使用哈希表。
<强> 4。明确的方式
如果元素的数量很少,如果其他代码编写速度更快,阅读速度更快,则编写适当的算法是没有用的。
EG。遍历数组中的每个独特元素(即第一个元素,第二个元素(第一个元素的副本已删除)等)删除所有相同的元素。 O(1)额外空间,O(n ^ 2)时间。
EG。使用执行此操作的库函数。效率取决于你容易获得的。
答案 6 :(得分:7)
嗯,它的基本实现非常简单。浏览所有元素,检查其余元素是否有重复,并将其余元素移到它们上面。
这是非常低效的,你可以通过输出或排序/二叉树的辅助数组加速它,但似乎不允许这样做。
答案 7 :(得分:6)
如果您被允许使用C ++,则拨打std::sort
,然后拨打std::unique
即可获得答案。时间复杂度为排序的O(N log N)和唯一遍历的O(N)。
如果C ++不在桌面上,那么就没有任何东西可以阻止这些算法用C语言编写。
答案 8 :(得分:6)
如果你愿意牺牲记忆,你可以在一次遍历中做到这一点。您可以简单地计算是否在哈希/关联数组中看到了整数。如果您已经看过一个数字,请在移动时删除它,或者更好的是,将未看到的数字移动到新数组中,避免在原始数组中移位。
Perl:
foreach $i (@myary) {
if(!defined $seen{$i}) {
$seen{$i} = 1;
push @newary, $i;
}
}
答案 9 :(得分:5)
函数的返回值应该是唯一元素的数量,它们都存储在数组的前面。如果没有这些附加信息,您甚至不知道是否有任何重复信息。
外循环的每次迭代都处理数组的一个元素。如果它是唯一的,它将保留在数组的前面,如果它是重复的,它将被数组中最后一个未处理的元素覆盖。该解决方案在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;
}
答案 10 :(得分:4)
这是一个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;
}
答案 11 :(得分:2)
让我们看看:
答案 12 :(得分:2)
这是我的解决方案。
///// 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] ;
}
}
答案 13 :(得分:2)
显然,数组应该从右到左“遍历”,以避免来回不必要地复制值。
如果你有无限的内存,你可以为sizeof(type-of-element-in-array) / 8
字节分配一个位数组,让每一位表示你是否已经遇到过相应的值。
如果不这样做,我想不出什么比遍历数组并将每个值与其后的值进行比较然后如果找到重复,则完全删除这些值。这是 O(n ^ 2)(或 O((n ^ 2-n)/ 2))附近的某个地方。
IBM有一个article有点接近主题。
答案 14 :(得分:1)
这是天真的(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;
}
答案 15 :(得分:1)
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);
}
}
答案 16 :(得分:1)
以下示例应解决您的问题:
def check_dump(x):
if not x in t:
t.append(x)
return True
t=[]
output = filter(check_dump, input)
print(output)
True
答案 17 :(得分:1)
以下情况如何?
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);
我尝试声明一个临时数组,并在将所有内容复制回原始数组之前将元素放入其中。
答案 18 :(得分:1)
在回顾问题之后,这是我的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;
答案 19 :(得分:1)
这可以使用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;
}
查看示例,由于生成的数组保留了原始元素顺序,因此这并不是所要求的。但如果放宽这个要求,上面的算法应该可以解决问题。
答案 20 :(得分:1)
在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;
}
}
}
答案 21 :(得分:0)
使用bloom过滤器进行散列。这将极大地减少内存开销。
答案 22 :(得分:0)
创建一个具有O(n)复杂度的BinarySearchTree
。
答案 23 :(得分:0)
在binary tree the disregards duplicates
- O(nlog(n))
中插入所有元素。然后通过遍历 - O(n)
将所有这些提取回数组中。我假设你不需要订单保存。
答案 24 :(得分:0)
在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}
希望这会有所帮助
答案 25 :(得分:0)
这可以在单次传递中完成,在O(N)时间内输入中的整数数量 list和O(N)存储的唯一整数数。
从前到后遍历列表,两个指针“dst”和 “src”初始化为第一项。从空哈希表开始 看到的“整数”。如果散列中不存在src处的整数, 将它写入dst的插槽并增加dst。在src添加整数 到散列,然后增加src。重复直到src通过结束 输入列表。
答案 26 :(得分:0)
首先,您应该创建一个数组check[n]
,其中n是要使其无重复的数组的元素数,并将每个元素(检查数组)的值设置为等于1。 for循环遍历带有重复项的数组,比如它的名字是arr
,并在for循环中写这个:
{
if (check[arr[i]] != 1) {
arr[i] = 0;
}
else {
check[arr[i]] = 0;
}
}
使用它,您可以将每个副本设置为零。所以剩下要做的就是遍历arr
数组并打印它不等于零的所有内容。订单保持不变,需要线性时间(3 * n)。
答案 27 :(得分:0)
给定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.
使用'key'在输出数组中维护其他元素。考虑密钥长度为O(n),对密钥执行排序所花费的时间和值为O(nlogn)。因此,从数组中删除所有重复项所花费的时间是O(nlogn)。
答案 28 :(得分:0)
这就是我所拥有的,虽然它错误地排列了我们可以按升序或降序排序以修复它的顺序。
#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");
}
答案 29 :(得分:-1)
这里写的一些答案非常简单(O(n ^ 2)或O(NlogN)中的排序和遍历)我假设这不是微软采访中的预期。显然,O(n)之上的任何答案都不是他们想要的。 更新声明不应该有任何帮助器数据结构,因此任何具有一个(哈希表,树,位数组或其他)的答案都不应该是有效的解决方案。
如果您可以分配额外的内存,那么Jeff B的答案可能是最简单的方法。 对于像这样的问题,我有一个很好的答案,但MAXINT需要受到数组大小的限制。 (示例:大小为100的数组可以包含1到100之间的任何数字。删除重复项作为原始问题)
在O(n)时间和O(1)记忆中的答案是:
// FLAG ALL DUPS IN THE ORIGIN ARRAY
int maxNumInArray = findMaxNumInArray(arr);
int dup = findMinNumInArray(arr) - 1;
for (int i=0; i < arrLength; ++i) {
int seekIndex = arr[i] % (maxNumInArray+1);
if (arr[seekIndex] > maxNumInArray)
arr[i] = dup; // invalidate index
else
arr[seekIndex] = arr[seekIndex] + maxNumInArray;
}
// REMOVE EMPTY SPACES
int i = 0;
int j = arrLength(arr)-1;
while (i<j) {
while (arr[i] != dup)
++i;
while (arr[j] == dup)
--j;
swap(arr[i], arr[j]);
}
如果你不知道界限,我的答案没有用,但你可以尝试使用它。 哦,这个具体的变化不会与负数一起使用,但修复它不是问题。
答案 30 :(得分:-1)
如果你有一个好的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;
答案 31 :(得分:-1)
对于想要在C ++中使用简单解决方案的人来说:
int* rmdup(int path[], int start, int end, int& newEnd) {
int ret[100];
newEnd = end;
int j = start;
for (int i = start; i < end; i++) {
if (path[i] == path[i+1]) {
newEnd--;
continue;
}
ret[j++] = path[i];
}
ret[j++] = path[end];
for(int i = start; i <= newEnd; i++)
path[i] = ret[i];
}
答案 32 :(得分:-1)
简单地使用变量x=arr[0]
并通过遍历其余元素来执行xor操作。如果元素重复,那么x将变为零。
这样我们就知道该元素先前已重复过。这也只需要o(n)
扫描原始数组中的所有元素。
答案 33 :(得分:-2)
Integer[] arrayInteger = {1,2,3,4,3,2,4,6,7,8,9,9,10};
Set set = new HashSet();
for(Integer i:arrayInteger)
set.add(i);
System.out.println(set);