4

I was asked this question in an interview:

How to shuffle a character array with no two duplicates next to each other?

The algorithm I came up with was :

  1. have a HashMap of Character, count of occurrence of Character pairs. With this find the count of duplicate vs unique elements.
  2. If duplicate > unique, cannot form a shuffled array with no 2 duplicate elements next to each other (?)
  3. if unique >= duplicate, have 2 stacks - 1 containing unique characters and one containing duplicate characters. Construct the resulting array in such a way that pop an element from unique stack first and pop an element from duplicate stack next. Repeat

For example:

[a,b,b,c] shuffled array with above algorithm - [a,b,c,b];

[b,b,b,c] unique < duplicate return error

But I am pretty sure I am overcomplicating the logic. Is there an easier and fool proof way to do this?

ruakh
  • 175,680
  • 26
  • 273
  • 307
Vall
  • 3,926
  • 3
  • 14
  • 14
  • if you have abcabc you can shuffle it to, for example, cbacab. So the test in (2) has false positives. But the procedure in (3) can't work if there aren't enough unique elements. – rici May 05 '16 at 00:33
  • 1
    See if my answer to this similar question is useful: http://stackoverflow.com/questions/25285792/generate-all-permutations-of-a-list-without-adjacent-equal-elements/32366585#32366585 – m69's been on strike for years May 05 '16 at 23:00

4 Answers4

4

[ there is a test cases failing as was pointed in the comments]
So take care of the same if referring my answer I see, but it doesn't seem to work if you have 'a'->4, 'b'->2, and 'c'->1. Because the first step is "abc", leaving 'a'->3 'b'->1. But there is an answer: "ababaca". – user3386109

Case 1 : Build the basic algorithm

  1. use a hashmap (key being the character and its occurance being the value) to count the occurances. This will give us buckets like if we have "cbaaba" will give 3 buckets with 'c' with value 1, 'a' with value 3 and 'b' with value 2.

  2. Sort the buckets based on the occurances with element with most occurancr first. so now we have

'a' -> 3 , 'b' -> 2 , 'c' -> 1

  1. Get the element from max occurance bucket, decrease its count by 1 in map and put it in result array. Follow this for subsequent occuance buckets based on the sorted occurance buckets.

result array will start with taking one each from 3 buckets in sorted fashion.

"abc" and now we have our buckets as 'a'->2 , 'b'-> 1 and 'c'-> 0

next we again try to get elemet each from sorted buckets (ignoring the buckets with 0 elements)

"abcab" now our buckets state become as : 'a'-> 1 , 'b'- > 0 and 'c'-> 0

next as above we proceed to have our result as

=> "abcaba".

Case 2 : if string is like "aaaabbbcccdd"

We will have buckets as

'a'--> 4
'b'--> 3
'c'--> 3
'd'--> 2

Here we have bucket of b and c having same size. When ever such situation occurs we have to perform an operation of JoinBuckets, It will join 'b' and 'c' in single bucket and 'bc' will be considered as a single element.

Now our buckets will be

'a'--> 4
'bc'--> 3
'd'--> 2

Now proceeding ahead in same manner as done in case 1, we try to build the result

=>result = "abcd"

'a'--> 3
'bc'--> 2
'd'--> 1

=>result = "abcdabcd"

'a'--> 2
'bc'--> 1
'd'--> 0

=>result = "abcdabcdabc"

'a'--> 1
'bc'--> 0
'd'--> 0

=>Final Result = "abcdabcdabca"

nits.kk
  • 5,204
  • 4
  • 33
  • 55
  • Not sure I understand the algorithm. How would this work for `aaaabbc`? – user3386109 May 05 '16 at 03:04
  • our buckets in sorted order od occurance are as : aaa, bb , c... we can proceed to make "abc" then buckets state : aa, b, now we proceed to make "abcab" with new bucket states: a, we finally proceed to make "abcaba"... I have edited my answer for better understanding. – nits.kk May 05 '16 at 03:24
  • I see, but it doesn't seem to work if you have 'a'->4, 'b'->2, and 'c'->1. Because the first step is "abc", leaving 'a'->3 'b'->1. But there is an answer: "ababaca". – user3386109 May 05 '16 at 03:33
  • 1
    agreed!! I will modify my answer ... I will take some time but will do it soon:) thanks for this test case – nits.kk May 05 '16 at 03:41
  • 1
    I think you can choose one with highest value, then select one with the second highest value. Put them in, and Iterate repetitively until none left. So, in the case a4,b2,c1. We construct "ab", left a3,b1,c1. Then "abab", left a2,b0,c1. Then "ababac", left a1,b0,c0. Then "ababaca". For a4b3c3d2 Case, we have "ab", a3b2c3d2, "abac", a2b2c2d2, "abacab", a1b1c2d2, "abacabcd", a1b1c1d1, "adacabcdabcd", all good. – LegitMe Jan 25 '17 at 20:21
1

I would start with a very simple algorithm:

public static void shuffleWithoutRepetition(char[] chars, Random rnd) {
  if (tooManySameCharacters(chars))
    throw new IllegalArgumentException("Too many same characters");

  do {
    shuffle(chars, rnd); // See java.util.Collections.shuffle
  } while (containsRepetition(chars));
}

That way, you have some working code, and when it turns out to be too slow, you can optimize it later.

  • This algorithm is very slow for shuffling aaaaaaaaaaaaaaabbbbbbbbbbbbbbb, which is bad.
  • It generates each possible output with the same probability, which is good.
Roland Illig
  • 40,703
  • 10
  • 88
  • 121
0

this is essentially a sorting question. It reminds me of 'peak finding' but in reverse, you want to in a sense 'create peaks' of unique items.

Also the logic of (2) is a bit off. If i have N repeated letters, I can still have no similar neighbors with N-2 of another letter:

'aaabb' -sort-> 'ababa'

this also demonstrates the issue of looking for 'uniques,' since you could very well have only letters that are duplicated somewhere in the array.

pseudo:

foreach letter in string{
  if next != self then happy, move to next

  if next == self, 
     loop until spot != self and swap

There may be a better way of divide and conquer to get through it in better time, but without actually testing it I'm not sure.

Alex Durbin
  • 196
  • 1
  • 7
  • 1
    Shuffling differs from sorting in that it is supposed to produce a different result every time, and in an ideal world all possible different results are equally likely. You could randomly shuffle the string and then try to fix it as per your algorithm, but (1) your algorithm does not work, and (2) it will favour certain permutations over other ones. It doesn't work because you might need to look on both sides of self to find a new place for it, so you need to check that the new place has no "self" neighbours. – rici May 05 '16 at 02:10
  • Yeah, I knew it wasn't perfect. Thanks for the feedback. – Alex Durbin May 05 '16 at 02:33
-1

SCALA Code. Copy and paste this program and run.

def rearrange(xs : List[Char]) : List[Char] ={
xs match {
    case Nil => Nil
    case x :: xs1 => xs1 match {
    case Nil => x :: Nil
    case y :: ys1 =>
         if (x == y)  { 
              (x :: rearrange(ys1)) :+ y 
         }
         else
             x :: y :: rearrange(ys1)
         }
     }
}                                               
rearrange: (xs: List[Char])List[Char]

rearrange("hello".toList)  
Jérôme
  • 1,254
  • 2
  • 20
  • 25