我正在考虑从(未排序的)字符串数组中删除重复项的最佳方法 - 该数组包含数百万或数千万字符串。数组已经预先填充,因此优化目标只是删除重复,而不是防止重复最初填充!!
我正在考虑进行排序然后二元搜索以获得log(n)搜索而不是n(线性)搜索。这将给我nlogn + n次搜索,这些搜索除了未排序(n ^ 2)之外的搜索效果更好,但这似乎仍然很慢。 (也正在考虑散列但不确定吞吐量)
请帮忙!寻找一种解决速度和内存的有效解决方案,因为在不使用Collections API的情况下涉及数百万个字符串!
答案 0 :(得分:7)
在你的最后一句话之前,答案对我来说显而易见:如果你需要保留顺序,请使用HashSet<String>
或LinkedHashSet<String>
:
HashSet<String> distinctStrings = new HashSet<String>(Arrays.asList(array));
如果您不能使用集合API,请考虑构建自己的哈希集...但在您给出原因之前,为什么您不想使用集合API,它是很难给出更具体的答案,因为这个原因也可以排除其他答案。
答案 1 :(得分:5)
<强>分析强>
让我们进行一些分析:
使用HashSet。时间复杂度 - O(n)。空间复杂度O(n)。请注意,它需要大约8 *个数组大小的字节(8-16个字节 - 对新对象的引用)。
快速排序。时间 - O(n * log n)。空间O(log n)(最差情况分别为O(n * n)和O(n))。
合并排序(二叉树/ TreeSet)。时间 - O(n * log n)。空间O(n)
堆排序。时间O(n * log n)。空间O(1)。 (但它比2和3慢)。
如果是Heap Sort,你可以在飞行中通过复制,所以你将在排序后保存最后一遍。
<强>结论强>
如果您关注时间,并且不介意为HashSet分配8 * array.length个字节 - 这个解决方案似乎是最佳的。
如果空间有问题 - 那么QuickSort +一次通过。
如果空间是一个大问题 - 实施一个堆,在飞行中丢弃重复。它仍然是O(n * log n)但没有额外的空间。
答案 2 :(得分:2)
我建议您在阵列上使用修改后的mergesort。在合并步骤中,添加逻辑以删除重复值。该解决方案具有n * log(n)复杂度,并且可以在需要时就地执行(在这种情况下,就地实现比使用普通mergesort更难,因为相邻部分可能包含已删除的重复项的间隙,这些空白也需要合并时关闭。)
有关mergesort的更多信息,请参阅http://en.wikipedia.org/wiki/Merge_sort
答案 3 :(得分:1)
创建一个处理此任务的哈希集太昂贵了。事实上,实际上他们告诉你不要使用Collections API的全部意义在于他们不想听到哈希这个词。所以这留下了代码。
请注意,在对数组进行排序后,您提供了二进制搜索:这没有任何意义,这可能是您的提案被拒绝的原因。
选项1:
public static void removeDuplicates(String[] input){
Arrays.sort(input);//Use mergesort/quicksort here: n log n
for(int i=1; i<input.length; i++){
if(input[i-1] == input[i])
input[i-1]=null;
}
}
选项2:
public static String[] removeDuplicates(String[] input){
Arrays.sort(input);//Use mergesort here: n log n
int size = 1;
for(int i=1; i<input.length; i++){
if(input[i-1] != input[i])
size++;
}
System.out.println(size);
String output[] = new String[size];
output[0]=input[0];
int n=1;
for(int i=1;i<input.length;i++)
if(input[i-1]!=input[i])
output[n++]=input[i];
//final step: either return output or copy output into input;
//here I just return output
return output;
}
选项3 :(由949300增加,基于选项1)。请注意,此会破坏输入数组,如果这是不可接受的,则必须进行复制。
public static String[] removeDuplicates(String[] input){
Arrays.sort(input);//Use mergesort/quicksort here: n log n
int outputLength = 0;
for(int i=1; i<input.length; i++){
// I think equals is safer, but are nulls allowed in the input???
if(input[i-1].equals(input[i]))
input[i-1]=null;
else
outputLength++;
}
// check if there were zero duplicates
if (outputLength == input.length)
return input;
String[] output = new String[outputLength];
int idx = 0;
for ( int i=1; i<input.length; i++)
if (input[i] != null)
output[idx++] = input[i];
return output;
}
答案 4 :(得分:0)
您好,您需要将它们放入数组中吗?使用像集合这样的哈希值来使用集合会更快。这里每个值都是唯一的,因为它的哈希值。
如果您将所有条目都设置为集合集合类型。
可以使用 HashSet(int initialCapacity)
构造函数,以防止在运行时扩展内存。
Set<T> mySet = new HashSet<T>(Arrays.asList(someArray))
如果不必扩展内存,则Arrays.asList()具有运行时O(n)。
答案 5 :(得分:0)
由于这是一个面试问题,我认为他们希望你提出自己的实现,而不是使用set api。
您可以构建二叉树并创建一个空数组来存储结果,而不是先对其进行排序并再次进行比较。
数组中的第一个元素是根。
如果下一个元素等于节点,则返回。 - &GT;这将删除重复的元素
如果下一个元素小于节点,则将其与左侧比较,否则将其与右侧进行比较。
继续执行上述两个步骤,直到到达树的末尾,然后您可以创建一个新节点并知道它还没有重复。 将此新节点值插入阵列。
在遍历原始数组的所有元素之后,您将获得一个数组的新副本,该副本在原始顺序中没有重复。
遍历需要O(n)并且搜索二叉树需要O(logn)(插入应该只取O(1),因为你只是附加它而不是重新分配/平衡树)所以总数应该是O (nlogn)。
答案 6 :(得分:0)
O.K。,如果他们想要超高速,让我们尽可能地使用字符串的哈希码。
循环遍历数组,获取每个String的哈希码,并将其添加到您喜欢的数据结构中。由于您不允许使用Collection,请使用BitSet。请注意,你需要两个,一个用于肯定,一个用于底片,每个都是巨大的。
使用另一个BitSet再次循环遍历数组。 True表示String传递。如果Bitset中不存在String的哈希码,则可以将其标记为true。否则,将其标记为可能重复,为false。当你在这里时,计算可能的重复数量。
将所有可能的重复项收集到一个名为possibleDuplicates的大字符串[]中。对它进行排序。
现在浏览原始数组中的可能重复项,并在possibleDuplicates中进行二进制搜索。如果存在,那么,你仍然被困住,因为你想要包括它而不是所有其他时间。所以你需要另一个阵列。凌乱,我必须去吃晚餐,但这是一个开始......