Aaron
Aaron

Reputation: 4480

Using an array and moving duplicates to end

I got this question at an interview and at the end was told there was a more efficient way to do this but have still not been able to figure it out. You are passing into a function an array of integers and an integer for size of array. In the array you have a lot of numbers, some that repeat for example 1,7,4,8,2,6,8,3,7,9,10. You want to take that array and return an array where all the repeated numbers are put at the end of the array so the above array would turn into 1,7,4,8,2,6,3,9,10,8,7. The numbers I used are not important and I could not use a buffer array. I was going to use a BST, but the order of the numbers must be maintained(except for the duplicate numbers). I could not figure out how to use a hash table so I ended up using a double for loop(n^2 horrible I know). How would I do this more efficiently using c++. Not looking for code, just an idea of how to do it better.

Upvotes: 6

Views: 3737

Answers (10)

sunjerry
sunjerry

Reputation: 51

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--;
        }
    }

If the input array is {1,7,4,8,2,6,8,3,7,9,10}, then the output is {1,7,4,8,2,6,10,3,9,7,8}. Comparing with your answer {1,7,4,8,2,6,3,9,10,8,7}, the first half is the same, while the right half is different, because I swap all duplicates with the tail of the array. As you mentioned, the order of the duplicates can be arbitrary.

Upvotes: 0

Pankaj Khattar
Pankaj Khattar

Reputation: 111

This can be done by iterating the array & marking index of the first change. later on swaping that mark index value with next unique value & then incrementing that mark index for next swap

Java Implementation:

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]);
        }

    }

output is 1 7 4 8 2 6 3 9 10 8 7

Upvotes: 1

Mr. Llama
Mr. Llama

Reputation: 20899

If you know the bounds on what the integer values are, B, and the size of the integer array, SZ, then you can do something like the following:

  1. Create an array of booleans seen_before with B elements, initialized to 0.
  2. Create a result array result of integers with SZ elements.
  3. Create two integers, one for front_pos = 0, one for back_pos = SZ - 1.
  4. Iterate across the original list:
    • Set an integer variable val to the value of the current element
    • If seen_before[val] is set to 1, put the number at result[back_pos] then decrement back_pos
    • If seen_before[val] is not set to 1, put the number at result[front_pos] then increment front_pos and set seen_before[val] to 1.

Once you finish iterating across the main list, all the unique numbers will be at the front of the list while the duplicate numbers will be at the back. Fun part is that the entire process is done in one pass. Note that this only works if you know the bounds of the values appearing in the original array.

Edit: It was pointed out that there's no bounds on the integers used, so instead of initializing seen_before as an array with B elements, initialize it as a map<int, bool>, then continue as usual. That should get you n*log(n) performance.

Upvotes: 2

Mooing Duck
Mooing Duck

Reputation: 66932

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
Not that I would turn in code this poorly commented. Also note that unordered_set probably uses it's own array internally, bigger than data. (This has been rewritten based on aix's answer, to be much faster)

Upvotes: 2

Naszta
Naszta

Reputation: 7744

#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

Check sort and unique.

Upvotes: 2

Chad
Chad

Reputation: 19032

It's ugly, but it meets the requirements of moving the duplicates to the end in place (no buffer array)

// 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;
      }
   }
}

Upvotes: 0

dreamlax
dreamlax

Reputation: 95335

I have been out of touch for a while, but I'd probably start out with something like this and see how it scales with larger input. I know you didn't ask for code but in some cases it's easier to understand than an explanation.

Edit: Sorry I missed the requirement that you cannot use a buffer array.

// 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;
}

Upvotes: 2

NPE
NPE

Reputation: 500495

In what follows:

  1. arr is the input array;
  2. seen is a hash set of numbers already encountered;
  3. l is the index where the next unique element will be placed;
  4. r is the index of the next element to be considered.

Since you're not looking for code, here is a pseudo-code solution (which happens to be valid 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

On your test case, this produces

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

Upvotes: 8

SomeoneRandom
SomeoneRandom

Reputation: 264

The way I would do this would be to create an array twice the size of the original and create a set of integers.

Then Loop through the original array, add each element to the set, if it already exists add it to the 2nd half of the new array, else add it to the first half of the new array.

In the end you would get an array that looks like: (using your example)

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

Then I would loop through the original array again and make each spot equal to the next non-null position (or 0'd or whatever you decided)

That would make the original array turn into your solution...

This ends up being O(n) which is about as efficient as I can think of

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.

Upvotes: 2

tune2fs
tune2fs

Reputation: 7705

I would use an additional map, where the key is the integer value from the array and the value is an integer set to 0 in the beginning. Now I would go through the array and increase the values in the map if the key is already in the map. In the end I would go again through the array. When the integer from the array has a value of one in the map, I would not change anything. When it has a value of 2 or more in the map I would swap the integer from the array with the last one.

This should result in a runtime of O(n*log(n))

Upvotes: 2

Related Questions