Assuming you want an equal distribution of the valid permutations:
If you do not care about memory or runtime complexity, you can generate all permutations of the string (Generating all permutations of a given string), then remove all Strings which have adjacent same letters, and finally pick a random one from that list.
If you do care about optimizing memory or runtime, see the similar questions linked.
Some other approaches if you do not need an equal distribution, but still a chance for any valid permutation to be found:
- You can build up the string by randomly picking from the remaining letters (similar as to what you did), and backtracking when you hit a dead end (no valid letter left to pick). See Python permutation using backtracking
- You can make a string where you pick random letters from the input without caring about adjacent repetitions, and then swap-shuffle (see Heap's algorithm) this random starting point iteratively until the result matches your condition (or until you're back to the start).
Backtracking solution
Here I pick a random nextChar, and try to build a random String that does not start with that char. If that fails, try another one. By recursion, this eventually tries out all combinations in a random order, until a valid one is found.
private static final Random RANDOM = new Random();
public static void main(String[] args) {
System.out.println(randomPerm("aaabcc"));
}
public static String randomPerm(String str) {
Map<Character, Long> counts = str
.chars()
.mapToObj(c -> (char) c)
.collect(groupingBy(Function.identity(), Collectors.counting()));
return restPerm(null, counts);
}
public static String restPerm(Character previous, Map<Character, Long> leftover) {
List<Character> leftKeys = new ArrayList<>(leftover.keySet());
while (!leftKeys.isEmpty()) {
Character nextChar = leftKeys.get(RANDOM.nextInt(leftKeys.size()));
leftKeys.remove(nextChar);
if (nextChar.equals(previous) || leftover.get(nextChar) == 0) {
continue; // invalid next char
}
// modify map in place, reduce count by one
Long count = leftover.compute(nextChar, (ch, co) -> co - 1);
if (leftover.values().stream().noneMatch(c -> c > 0)) {
return nextChar.toString(); // last char to use
}
String rest = restPerm(nextChar, leftover); // recurse
if (rest != null) {
return nextChar + rest; // solution found
}
leftover.put(nextChar, count + 1);
}
return null;
}
This solution will more likely find results where rare characters are used in the beginning of the result, so the distribution would not be equal. As an example input "aaaaaaabbbbbc" will have c more often on the left than on the right, like "acabababababa" in 50% of cases. But it uses limited memory and may complete before computing all permutations, and it is somewhat similar to your approach.