I see a solution with Simple Permutation in Combinatorial Analysis .
First we should get the combination of the elements in the example: A 1 , A 2 , > 3 C 2 , C 1 C 3 , D 1 . Then delete the ones that have adjacent equal letters.
Well, the permutation has a 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 only generating 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 building a combination and have A
, we will skip the combination AA
and all derivations of it ( AAB
, AAC
, ...).
Ignoring iterations with repeated letters. Although we have 4 A letters, we do not need to test the combinations starting with all of them. We just need to test an element corresponding to each letter.
To tell you the truth, I do not know how to analyze the order of complexity of this optimization, but what I can say is that the more repeated items there are, the fewer possibilities will be tested and the faster we will get the result.
Since% is the number of letters (ignoring repetition) and m
the total number of elements (counting repeats), we can say that in the worst case, if we have an element of each letter ( n
, the algorithm will be m == n)
, but if we have repetition ( O(n!)
), the complexity will be greater than m < n
and less than O(m!)
.
I made the following Java implementation:
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 O(n!)
and n = 10
. This is consistent with the algorithm's return:
-
m = 4
combinations
-
% with "dead ends", ie situations where there were no letters available to continue and the process was interrupted