使用数组并移动重复项结束

时间:2011-10-18 19:58:58

标签: c++ arrays sorting duplicates theory

我在接受采访时得到了这个问题,最后被告知有一种更有效的方法可以做到这一点,但仍然无法弄明白。您正在向函数传递一个整数数组和一个数组大小的整数。在数组中,您有很多数字,有些数字重复,例如1,7,4,8,2,6,8,3,7,9,10。您想要获取该数组并返回一个数组,其中所有重复的数字都放在数组的末尾,因此上面的数组将变为1,7,4,8,2,6,3,9,10,8,7。我使用的数字并不重要,我不能使用缓冲区数组。我打算使用BST,但必须保持数字的顺序(重复的数字除外)。我无法弄清楚如何使用哈希表,所以我最终使用了一个双循环(n ^ 2可怕,我知道)。如何使用c ++更有效地完成此操作。不寻找代码,只是想知道如何做得更好。

10 个答案:

答案 0 :(得分:8)

以下内容:

  1. arr是输入数组;
  2. seen是已遇到的数字哈希集;
  3. l是放置下一个唯一元素的索引;
  4. r是要考虑的下一个元素的索引。
  5. 由于您不是在寻找代码,因此这是一个伪代码解决方案(恰好是有效的Python):

    arr = [1,7,4,8,2,6,8,3,7,9,10]
    seen = set()
    l = 0
    r = 0
    while True:
      # advance `r` to the next not-yet-seen number
      while r < len(arr) and arr[r] in seen:
        r += 1
      if r == len(arr): break
      # add the number to the set
      seen.add(arr[r])
      # swap arr[l] with arr[r]
      arr[l], arr[r] = arr[r], arr[l]
      # advance `l`
      l += 1
    print arr
    

    在您的测试用例中,这会产生

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

答案 1 :(得分:2)

我这样做的方法是创建一个两倍于原始大小的数组并创建一组整数。

然后遍历原始数组,将每个元素添加到集合中,如果它已经存在,则将其添加到新数组的后半部分,否则将其添加到新数组的前半部分。

最后,您将获得一个类似于以下内容的数组:(使用您的示例)

1,7,4,8,2,6,3,9,10, - , - ,8,7, - , - , - , - , - , - , - , - , -

然后我会再次遍历原始数组并使每个点等于下一个非空位置(或者0或者您决定的任何位置)

这会使原始阵列变成你的解决方案......

这最终是O(n),这和我想象的一样高效

Edit: since you can not use another array, when you find a value that is already in the
set you can move every value after it forward one and set the last value equal to the
number you just checked, this would in effect do the same thing but with a lot more operations.

答案 2 :(得分:2)

我会使用另一个映射,其中键是数组中的整数值,值是在开头设置为0的整数。现在,如果键已经在地图中,我将遍历数组并增加地图中的值。 最后我会再次通过阵列。当数组中的整数在地图中的值为1时,我不会更改任何内容。当它在地图中的值为2或更多时,我会将数组中的整数与最后一个交换。

这应该导致O(n * log(n))

的运行时间

答案 3 :(得分:2)

void remove_dup(int* data, int count) {
    int* L=data; //place to put next unique number
    int* R=data+count; //place to place next repeat number
    std::unordered_set<int> found(count); //keep track of what's been seen
    for(int* cur=data; cur<R; ++cur) { //until we reach repeats
        if(found.insert(*cur).second == false) { //if we've seen it
            std::swap(*cur,*--R); //put at the beginning of the repeats
        } else                    //or else
            std::swap(*cur,*L++); //put it next in the unique list
    }
    std::reverse(R, data+count); //reverse the repeats to be in origional order
}

http://ideone.com/3choA
不是说我会把这个评论很差的代码交给我。另请注意,unordered_set可能在内部使用它自己的数组,大于data。 (这已根据aix的答案重写,要快得多)

答案 4 :(得分:2)

如果您知道整数值的范围B以及整数数组SZ的大小,那么您可以执行以下操作:

  1. 创建一个包含seen_before元素的布尔B数组,初始化为0。
  2. 使用result元素创建整数结果数组SZ
  3. 创建两个整数,一个用于front_pos = 0,一个用于back_pos = SZ - 1
  4. 遍历原始列表:
    • 将整数变量val设置为当前元素的值
    • 如果seen_before[val]设置为1,请将号码设为result[back_pos],然后递减back_pos
    • 如果seen_before[val]未设置为1,请将号码设为result[front_pos],然后将front_pos增加,并将seen_before[val]设置为1。
  5. 完成主列表的迭代后,所有唯一的数字将位于列表的前面,而重复的数字将位于后面。有趣的是,整个过程一次完成。请注意,这仅在您知道原始数组中出现的值的边界时才有效。

    编辑:有人指出所使用的整数没有界限,因此不是将seen_before初始化为具有B元素的数组,而是将其初始化为{ {1}},然后像往常一样继续。这应该会让你获得n * log(n)的表现。

答案 5 :(得分:2)

#include <algorithm>

T * array = [your array];
size_t size = [array size];
                                           // Complexity:
sort( array, array + size );               // n * log(n) and could be threaded
                                           // (if merge sort)
T * last = unique( array, array + size );  // n, but the elements after the last
                                           // unique element are not defined

检查sortunique

答案 6 :(得分:2)

我已经失去了一段时间,但我可能会从这样的事情开始,看看它如何随着更大的输入而扩展。我知道你没有要求代码,但在某些情况下,它比解释更容易理解。

编辑:对不起我错过了你不能使用缓冲阵列的要求。

// returns new vector with dupes a the end
std::vector<int> move_dupes_to_end(std::vector<int> input)
{
    std::set<int> counter;
    std::vector<int> result;
    std::vector<int> repeats;

    for (std::vector<int>::iterator i = input.begin(); i < input.end(); i++)
    {
        if (counter.find(*i) == counter.end())
            result.push_back(*i);
        else
            repeats.push_back(*i);
        counter.insert(*i);
    }

    result.insert(result.end(), repeats.begin(), repeats.end());

    return result;
}

答案 7 :(得分:1)

这可以通过迭代阵列和放大来完成。标记第一个变化的索引。 稍后将该标记索引值与下一个唯一值交换 &安培;然后为下一个交换增加该标记索引

Java实施:

public static void solve() {
                Integer[] arr = new Integer[] { 1, 7, 4, 8, 2, 6, 8, 3, 7, 9, 10 };
        final HashSet<Integer> seen = new HashSet<Integer>();
        int l = -1;

        for (int i = 0; i < arr.length; i++) {
            if (seen.contains(arr[i])) {
                if (l == -1) {
                    l = i;
                }
                continue;
            }
            if (l > -1) {
                final int temp = arr[i];
                arr[i] = arr[l];
                arr[l] = temp;
                l++;
            }
            seen.add(arr[i]);
        }

    }

输出为1 7 4 8 2 6 3 9 10 8 7

答案 8 :(得分:0)

这很难看,但它符合将副本移到最后的要求(无缓冲阵列)

// warning, some light C++11
void dup2end(int* arr, size_t cnt)
{
   std::set<int> k;
   auto end = arr + cnt-1;
   auto max = arr + cnt;
   auto curr = arr;

   while(curr < max)
   {
      auto res = k.insert(*curr);

      // first time encountered
      if(res.second)
      {
         ++curr;
      }
      else
      {
         // duplicate:
         std::swap(*curr, *end);
         --end;
         --max;
      }
   }
}

答案 9 :(得分:0)

void move_duplicates_to_end(vector<int> &A) {
    if(A.empty()) return;
    int i = 0, tail = A.size()-1;
    while(i <= tail) {
        bool is_first = true;    // check of current number is first-shown
        for(int k=0; k<i; k++) { // always compare with numbers before A[i]
            if(A[k] == A[i]) {
                is_first = false;
                break;
            }
        }
        if(is_first == true) i++;
        else {
            int tmp = A[i]; // swap with tail
            A[i] = A[tail];
            A[tail] = tmp;
            tail--;
        }
    }

如果输入数组为{1,7,4,8,2,6,8,3,7,9,10},则输出为{1,7,4,8,2,6,10 ,3,9,7,8}。与你的答案{1,7,4,8,2,6,3,9,10,8,7}相比,前半部分是相同的,而右半部分是不同的,因为我将所有副本与尾部交换数组。如您所述,重复的顺序可以是任意的。