有效地从java中的大型数组中删除重复的字符串?

时间:2012-04-06 15:29:24

标签: java

我正在考虑从(未排序的)字符串数组中删除重复项的最佳方法 - 该数组包含数百万或数千万字符串。数组已经预先填充,因此优化目标只是删除重复,而不是防止重复最初填充!!

我正在考虑进行排序然后二元搜索以获得log(n)搜索而不是n(线性)搜索。这将给我nlogn + n次搜索,这些搜索除了未排序(n ^ 2)之外的搜索效果更好,但这似乎仍然很慢。 (也正在考虑散列但不确定吞吐量)

请帮忙!寻找一种解决速度和内存的有效解决方案,因为在不使用Collections API的情况下涉及数百万个字符串!

7 个答案:

答案 0 :(得分:7)

在你的最后一句话之前,答案对我来说显而易见:如果你需要保留顺序,请使用HashSet<String>LinkedHashSet<String>

HashSet<String> distinctStrings = new HashSet<String>(Arrays.asList(array));

如果您不能使用集合API,请考虑构建自己的哈希集...但在您给出原因之前,为什么您不想使用集合API,它是很难给出更具体的答案,因为这个原因也可以排除其他答案。

答案 1 :(得分:5)

<强>分析

让我们进行一些分析:

  1. 使用HashSet。时间复杂度 - O(n)。空间复杂度O(n)。请注意,它需要大约8 *个数组大小的字节(8-16个字节 - 对新对象的引用)。

  2. 快速排序。时间 - O(n * log n)。空间O(log n)(最差情况分别为O(n * n)和O(n))。

  3. 合并排序(二叉树/ TreeSet)。时间 - O(n * log n)。空间O(n)

  4. 堆排序。时间O(n * log n)。空间O(1)。 (但它比2和3慢)。

  5. 如果是Heap Sort,你可以在飞行中通过复制,所以你将在排序后保存最后一遍。

    <强>结论

    1. 如果您关注时间,并且不介意为HashSet分配8 * array.length个字节 - 这个解决方案似乎是最佳的。

    2. 如果空间有问题 - 那么QuickSort +一次通过。

    3. 如果空间是一个大问题 - 实施一个堆,在飞行中丢弃重复。它仍然是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。

您可以构建二叉树并创建一个空数组来存储结果,而不是先对其进行排序并再次进行比较。

数组中的第一个元素是根。

  1. 如果下一个元素等于节点,则返回。 - &GT;这将删除重复的元素

  2. 如果下一个元素小于节点,则将其与左侧比较,否则将其与右侧进行比较。

  3. 继续执行上述两个步骤,直到到达树的末尾,然后您可以创建一个新节点并知道它还没有重复。 将此新节点值插入阵列。

    在遍历原始数组的所有元素之后,您将获得一个数组的新副本,该副本在原始顺序中没有重复。

    遍历需要O(n)并且搜索二叉树需要O(logn)(插入应该只取O(1),因为你只是附加它而不是重新分配/平衡树)所以总数应该是O (nlogn)。

答案 6 :(得分:0)

O.K。,如果他们想要超高速,让我们尽可能地使用字符串的哈希码。

  1. 循环遍历数组,获取每个String的哈希码,并将其添加到您喜欢的数据结构中。由于您不允许使用Collection,请使用BitSet。请注意,你需要两个,一个用于肯定,一个用于底片,每个都是巨大的。

  2. 使用另一个BitSet再次循环遍历数组。 True表示String传递。如果Bitset中不存在String的哈希码,则可以将其标记为true。否则,将其标记为可能重复,为false。当你在这里时,计算可能的重复数量。

  3. 将所有可能的重复项收集到一个名为possibleDuplicates的大字符串[]中。对它进行排序。

  4. 现在浏览原始数组中的可能重复项,并在possibleDuplicates中进行二进制搜索。如果存在,那么,你仍然被困住,因为你想要包括它而不是所有其他时间。所以你需要另一个阵列。凌乱,我必须去吃晚餐,但这是一个开始......