I see a solution with Permutation in Combinatorial Analysis.
First we must get the combination of the elements in the example: To1, To2, To3, To4, B1, B2, C1, C2, C3, D1. Then simply delete those with adjacent identical letters.
Well, permutation has factorial order O(n!)
(being n
the number of elements) and 10!
of the example would generate 3.628.800
of possibilities.
However, we can optimize the algorithm by generating only the combinations that matter, in two ways:
- Ignoring a combination (and all derived combinations) if the next letter repeats the current one. For example, if we are mounting a combination and we have
A
, we’ll skip the combination AA
and all its derivations (AAB
, AAC
, ...).
- Ignoring iterations with repeated letters. Although we have 4 letters To, we don’t need to test the combinations starting with all of them. We only need to test one element corresponding to each letter.
To tell you the truth I don’t know how to analyze the order of complexity of this optimization, but what I can say is that the more repeat items there are, the less possibilities will be tested and the faster we will get the result.
Being m
the number of letters (ignoring the repetition) and n
the total number of elements (counting the repeated ones), we can say that, in the worst case, if we have an element of each letter (m == n)
, the algorithm will be effectively O(n!)
. But if we have repetition (m < n
), the complexity will be greater than O(m!)
and less than O(n!)
.
I made the following implementation in Java:
public class Distribuicao {
private List<String> combinacoes = new ArrayList<String>();
private List<String> becos = new ArrayList<String>();
private long combinacoesTestadas = 0;
/**
* Começa pegando as letras uma a uma e combinando o restante.
* Note que assim já não precisamos verificar as combinações repetidas,
* já que usamos a chave única do mapa.
*/
public Distribuicao(HashMap<Character, Integer> itens) {
for (Entry<Character, Integer> e : itens.entrySet()) {
trocarECombinar(itens, e, "");
}
}
/**
* Procura novas combinações, ignorando se repetir a letras
*/
private void combinar(HashMap<Character, Integer> itens, String prefixo) {
Character ultimo = prefixo.charAt(prefixo.length() - 1);
boolean itensSobrando = false;
boolean encontrouNovaPossibilidade = false;
//verifica cada letra, sem repetir
for (Entry<Character, Integer> e : itens.entrySet()) {
//verifica se ainda há um elemento disponível da letra
if (e.getValue() > 0) {
itensSobrando = true;
//verifica se é igual à anterior
if (!e.getKey().equals(ultimo)) {
encontrouNovaPossibilidade = true;
//tenta uma nova combinação com a letra atual
trocarECombinar(itens, e, prefixo);
}
}
}
if (!itensSobrando) {
combinacoesTestadas++;
combinacoes.add(prefixo);
} else if (!encontrouNovaPossibilidade) {
combinacoesTestadas++;
becos.add("[prefixo = " + prefixo + ", resto = " + itens + "]");
}
}
/**
* Decrementa a letra usada, acrescenta a atual no prefixo
* e tenta recursivamente uma nova combinação
*/
private void trocarECombinar(
HashMap<Character, Integer> itens,
Entry<Character, Integer> e,
String prefixo) {
e.setValue(e.getValue() - 1);
combinar(itens, prefixo + e.getKey().toString());
e.setValue(e.getValue() + 1);
}
public List<String> getCombinacoes() {
return combinacoes;
}
public List<String> getBecos() {
return becos;
}
public long getCombinacoesTestadas() {
return combinacoesTestadas;
}
public static void main(String[] args) {
//prepara o mapa de teste
HashMap<Character, Integer> mapa = new HashMap<Character, Integer>();
mapa.put('A', 4);
mapa.put('B', 2);
mapa.put('C', 3);
mapa.put('D', 1);
//executa a distribuição
Distribuicao d = new Distribuicao(mapa);
//quantidade de combinações testadas
System.out.println("Combinações testadas: " + d.getCombinacoesTestadas());
//combinações encontradas
System.out.println("Combinações encontradas: " + d.getCombinacoes().size());
for (String combinacao : d.getCombinacoes()) {
System.out.println(" -> " + combinacao);
}
//combinações onde não houve como continuar
System.out.println("Becos sem saída: " + d.getBecos().size());
for (String beco : d.getBecos()) {
System.out.println(" -> " + beco);
}
}
}
In the example, we have n = 10
and m = 4
. This is consistent with the return of the algorithm:
1074
combinations
462
""dead ends, that is, situations where there were no letters available to continue and the process was stopped
The stopping condition could be without position possible.
– Felipe Avelar