用0-(N-1)中的唯一数字替换重复的数字

时间:2012-04-06 08:18:05

标签: java algorithm

背景:

我有一个N长度的正随机数组,肯定包含重复项。 例如10,4,5,7,10,9,10,9,8,10,5
修改 N很可能是32,或者是其他一些关于该大小的其他权力。

问题:

我正在尝试找到用0-(N-1)中缺少的数字替换重复项的最快方法。使用上面的例子,我想要一个如下所示的结果:
10,4,5,7,0,9,1,2,8,3,6
目标是让每个数字中的一个从0到N-1,而不是用0-(N-1)替换所有数字(随机顺序很重要)。
编辑此替换是确定性的也很重要,即相同的输入将具有相同的输出(非随机)。

我的解决方案:

目前在Java中实现,使用2个布尔数组来跟踪已使用/未使用的数字(在[0,N)范围内的唯一数字/缺失数字),并且具有N + N * sqrt的近似最坏情况运行时(N)。
代码如下:

public byte[] uniqueify(byte[] input)
{
    boolean[] usedNumbers = new boolean[N];
    boolean[] unusedIndices = new boolean[N];
    byte[] result = new byte[N];

    for(int i = 0; i < N; i++) // first pass through
    {
        int newIdx = (input[i] + 128) % N; // first make positive
        if(!usedNumbers[newIdx]) // if this number has not been used
        {
            usedNumbers[newIdx] = true; // mark as used
            result[i] = newIdx; // save it in the result
        }
        else // if the number is used
        {
            unusedIndices[i] = true; // add it to the list of duplicates
        }
    }

    // handle all the duplicates
    for(int idx = 0; idx < N; idx++) // iterate through all numbers
    {
        if(unusedIndices[idx]) // if unused
            for(int i = 0; i < N; i++) // go through all numbers again
            {
                if(!usedNumbers[i]) // if this number is still unused
                {
                    usedNumbers[i] = true; // mark as used
                    result[i] = idx;
                    break;
                }
            }
    }
    return result;
}  

这似乎是我所希望的最快,但我想我会问互联网,因为有些人比我更聪明可能有更好的解决方案。

N.B。建议/解决方案不必是Java。

谢谢。

编辑我忘了提到我正在将其转换为C ++。我发布了我的java实现,因为它更完整。

7 个答案:

答案 0 :(得分:5)

使用balanced binary search tree跟踪已使用/未使用的数字而不是布尔数组。那么你的运行时间将为n log n

最直接的解决方案是:

  1. 浏览列表并构建“未使用的”BST
  2. 第二次浏览列表,跟踪目前在“二手BST”中看到的数字
  3. 如果找到重复项,请将其替换为“未使用的”BST的随机元素。

答案 1 :(得分:2)

以下是我的写作方式。

public static int[] uniqueify(int... input) {
    Set<Integer> unused = new HashSet<>();
    for (int j = 0; j < input.length; j++) unused.add(j);
    for (int i : input) unused.remove(i);
    Iterator<Integer> iter = unused.iterator();
    Set<Integer> unique = new LinkedHashSet<>();
    for (int i : input)
        if (!unique.add(i))
            unique.add(iter.next());
    int[] result = new int[input.length];
    int k = 0;
    for (int i : unique) result[k++] = i;
    return result;
}

public static void main(String... args) {
    System.out.println(Arrays.toString(uniqueify(10, 4, 5, 7, 10, 9, 10, 9, 8, 10, 5)));
}

打印

[10, 4, 5, 7, 0, 9, 1, 2, 8, 3, 6]

答案 2 :(得分:1)

我的方法是 1.将数组复制到Java中的Set。

Set会以最快的复杂性自动删除重复项(因为Sun Micro已经实现了它,通常他们的方法是最快的...使用TimSort进行排序等...)

  1. 计算集合的大小()。

  2. 大小不会给你重复。

  3. 现在将数组0-n-1复制到同一组...将插入缺失值。

答案 3 :(得分:1)

最快的方法可能是最直接的方法。我将通过数据列表,保持每个不同值的计数,并标记重复出现的位置。然后,只需要形成一个未使用的值列表,并在发现重复的地方轮流应用它们。

使用C ++ List可能很有诱惑力,如果速度至关重要,那么简单的C数组效率最高。

该程序显示原则。

#include <iostream>
#include <cstring>

using namespace std;

int main()
{
  int data[] = { 10, 4, 5, 7, 10, 9, 10, 9, 8, 10, 5 };
  int N = sizeof(data) / sizeof(data[0]);

  int tally[N];
  memset(tally, 0, sizeof(tally));

  int dup_indices[N];
  int ndups = 0;

  // Build a count of each value and a list of indices of duplicate data
  for (int i = 0; i < N; i++) {
    if (tally[data[i]]++) {
      dup_indices[ndups++] = i;
    }
  }

  // Replace each duplicate with the next value having a zero count
  int t = 0;
  for (int i = 0; i < ndups; i++) {
    while (tally[t]) t++;
    data[dup_indices[i]] = t++;
  }

  for (int i = 0; i < N; i++) {
    cout << data[i] << " ";
  }

  return 0;
}

<强>输出

10 4 5 7 0 9 1 2 8 3 6

答案 4 :(得分:0)

我认为甚至可以使用n的运行时间。我们的想法是跟踪原始列表中使用的项目以及处理期间在单独数组中使用的其他项目。可能的java实现如下所示:

int[] list = { 10, 4, 5, 7, 10, 9, 10, 9, 8, 10, 5 };

boolean[] used = new boolean[list.length];
for (int i : list) {
    used[i] = true;
}

boolean[] done = new boolean[list.length];
int nextUnused = 0;

Arrays.fill(done, false);

for (int idx = 0; idx < list.length; idx++) {
    if (done[list[idx]]) {
        list[idx] = nextUnused;
    }
    done[list[idx]] = true;
    while (nextUnused < list.length && (done[nextUnused] || used[nextUnused])) {
        nextUnused++;
    }
}

System.out.println(Arrays.toString(list));

答案 5 :(得分:0)

List<Integer> needsReplaced = newLinkedList<Integer>();
boolean[] seen = new boolean[input.length];

for (int i = 0; i < input.length; ++i) {
    if (seen[input[i]]) {
        needsReplaced.add(i);
    } else {
        seen[input[i]] = true;
    }

}

int replaceWith = 0;
for (int i : needsReplaced) {
    while (seen[replaceWith]) {
        ++replaceWith;
    }
    input[i] = replaceWith++;
}

这应该在大约2n。列表操作是常量时间,即使第二个循环看起来是嵌套的,外部循环运行时间明显少于n次迭代,内部循环总共只运行n次。

答案 6 :(得分:0)

C#但应该很容易转换为java。 O(n)。

        int[] list = { 0, 0, 6, 0, 5, 0, 4, 0, 1, 2, 3 };
        int N = list.length;

        boolean[] InList = new boolean[N];
        boolean[] Used = new boolean[N];
        int[] Unused = new int[N];

        for (int i = 0; i < N; i++) InList[list[i]] = true;
        for (int i = 0, j = 0; i < N; i++) 
            if (InList[i] == false)
                Unused[j++] = i;

        int UnusedIndex = 0;
        for (int i = 0; i < N; i++)
        {
            if (Used[list[i]] == true)
                list[i] = Unused[UnusedIndex++];
            Used[list[i]] = true;
        }

编辑:尝试将其从c#转换为java。我这里没有java所以它可能无法编译但应该很容易修复。如果java不自动执行此操作,则可能需要将数组初始化为false。