Indicazioni pratiche:
Una soluzione è mantenere le informazioni minime per rappresentare una combinazione e poi farla "progredire" di volta in volta andando avanti finché si arriva all'ultima combinazione possibile. Per fare questo, il minimo necessario è tenere un array int[] poiché il numero
n di cifre è richiesto come "arbitrario", oltre al fatto che anche la base è arbitraria (e un int è assolutamente sufficiente ... anzi, anche troppo ma è ok).
Su come strutturare il codice, mi vengono in mente 2 soluzioni: un grosso ciclo al cui interno c'è tutto (logica, stampa dei dati, ecc...) oppure la gestione in stile a "iteratore".
Un grosso ciclo con tutto quanto dentro non è di per sé sbagliato ma è meno bello dal punto di vista del design (c'è tutto mescolato dentro, logica, stampe, ecc...) oltre al fatto che non è (facilmente) riutilizzabile.
Una alternativa è incapsulare la logica in una classe apposita e poi offrire il modo per avanzare nelle combinazioni usando lo stile degli "iteratori". A livello pratico un
uso finale di questo tipo:
GrayCounter grayCounter = new GrayCounter(3, 4); // base 3, n.cifre=4
while (grayCounter.hasNext()) {
int[] combinazione = grayCounter.next();
// usa la combinazione (stampa ecc..)....
}
Questo è decisamente più pulito e interessante.
Riguardo la logica per far progredire le combinazioni, a me viene in mente di ragionare su come le singole cifre debbano incrementarsi o decrementarsi. Questo sarebbe sicuramente utile nello scenario della classe che funziona "a iteratore", poiché c'è da mantenere uno "stato" su come le combinazioni devono evolvere.
Basta osservare la sequenza già postata: 00 01 02 03 04 14 13 12 11 10 20 21 22 23 24 34 33 32 31 30 40 41 42 43 44
La cifra di destra, prima si incrementa da 0 a 4, poi quando quella di sinistra si incrementa di 1, quella di destra si decrementa da 4 a 0, poi quando quella di sinistra si incrementa di nuovo quella di destra torna ad incrementarsi da 0 a 4 ecc...
Provate a scrivere "a mano" le combinazioni con base 3 e con 3 o 4 cifre. Se osservate come avvengono gli incrementi e decrementi, scoprirete la regola generale.
Dovessi farlo io (e senza indicazioni o imposizioni particolari), lo farei nello stile a "iteratore" e all'interno della classe manterrei le seguenti informazioni: a) la base (ovviamente), b) un array int[n] e c) un array boolean[n]. Eventualmente (dovrei ragionare se è strettamente necessario) la indicazione se le combinazioni sono terminate oppure non ancora.
A che serve l'array di boolean? Io lo userei per indicare se una colonna (cifra) si deve incrementare o decrementare. E la logica per stabilirlo è deducibile osservando come avvengono gli incrementi/decrementi in uno scenario come quello descritto poco fa.