Champaign Fountain Puzzle


30

I bicchieri d'acqua vuoti sono disposti nel seguente ordine:

inserisci qui la descrizione dell'immagine

Quando si versa del liquido nel 1 ° bicchiere, se è pieno, il liquido in eccesso viene fatto fluire nei bicchieri 2 e 3 in uguali quantità. Quando il bicchiere 2 è pieno, il liquido in eccesso viene trasportato in 4 e 5 e così via.

Dato un N litri di liquido e la capacità massima di ciascun bicchiere è di 1 litro, fornire la quantità di liquido presente in ogni bicchiere se si svuotano N litri di liquido versando nel bicchiere completando la funzione in getWaterInBucket(int N, int X)cui X è il numero di vetro. Quindi, per esempio, se voglio avere 4 litri all'inizio e voglio trovare l'acqua nel bicchiere 3, la funzione ègetWaterInBucket(4, 3)

Come posso risolverlo a livello di codice? Ho provato a trovare una soluzione matematica usando il triangolo di Pascal. Questo non ha funzionato. L'ho considerato un albero in modo da poter aggiungere un parametro come questo getWaterInBucket(BTree root, int N, int X)e quindi provare una soluzione ricorsiva per ogni livello, ma i parametri non sono consentiti in questo problema. C'è qualcosa di ovvio, qualche trucco?


18
Non vorrei lavorare in un'azienda in cui i problemi del management riguardano le fontane di champagne ...
mouviciel,

Puoi mai versare in un bicchiere diverso dal bicchiere 1? In caso contrario, ogni livello avrà la stessa quantità di acqua in ciascun bicchiere. Pertanto, avrai livelli completi ogni volta che verserai 1, 3, 6, 10 ... litri. Se versi 7 litri, la quarta fila ha 4 bicchieri, quindi ognuno sarà 1/4 pieno. Tutti i livelli sopra saranno pieni.
GlenPeterson,

5
@GlenPeterson Da come lo sto leggendo non penso che si riempirebbero allo stesso modo. Sì, 2 e 3 si riempirebbero allo stesso modo perché hanno solo una cosa che si riversa in loro ma una volta che sono pieni 2 si versa ugualmente in 4/5 e 3 si riversa ugualmente in 5/6, quindi 5 viene riempito al doppio del ratto di 4/6 . Le coppe centrali si riempiono sempre più velocemente delle coppe esterne. quando il 4/6 è pieno, l'8 / 9 è pieno del 25% e il 7/10 è ancora vuoto.
Brad,

1
Inoltre, questo mi ricorda il Triangolo di Pascal ..
Brad,

@mouviciel Haha GlenPeterson - Il primo bicchiere da versare è sempre il bicchiere 1. L'intervistatore ha anche affermato di utilizzare queste informazioni. Sembrava più confuso di quanto non fossi sulla risposta giusta a questo problema.
Slartibartfast,

Risposte:


35

Hai solo bisogno di simulare il versamento, qualcosa del genere

void pour(double glasses[10], int glass, double quantity)
{
    glasses[glass] += quantity;
    if(glasses[glass] > 1.0)
    {
         double extra = glasses[glass] - 1.0;
         pour( glasses, left_glass(glass), extra / 2 );
         pour( glasses, right_glass(glass), extra / 2 );
         glasses[glass] = 1.0;
    }
}

double getWaterInGlass(int N, int X)
{
    double glasses[10] = {0,0,0,0,0,0};
    pour(glasses, 0, X);
    return glasses[N];
}

Allo stato attuale, questo non è un albero. Perché bicchieri diversi si riversano negli stessi bicchieri, ciò gli impedisce di essere un albero.


16
+1 per la grande osservazione che questo non è un albero.
Mihai Danila,

2
Buona risposta. In un'intervista, dovresti dire che questo potrebbe avere problemi di scalabilità perché calcola il contenuto di tutti gli occhiali. Inoltre, è necessario gestire il caso in cui l'acqua fuoriesce dalla fila inferiore di bicchieri. E tu vuoi return glasses[N-1], perché i numeri di vetro iniziano da 1 invece di 0.
Tom Panning

1
Penso che la parte impegnativa potrebbe essere quella di capire gli indici dei bambini di destra e di sinistra. Se lo hai presentato, l'intervistatore ti chiederà di implementare quelle funzioni. Potrebbe esserci una formula esplicita.
James,

Questa è una soluzione davvero elegante. Grazie. Le sarei grato se potessi modificarlo per aggiungere commenti a righe di codice per spiegare cosa significhi ogni passaggio nel processo di pensiero. Anche il numero di occhiali non è limitato a 10. Potrebbe essere qualsiasi cosa
Slartibartfast,

1
Come trovi gli occhiali sinistro e destro?
kiewic,

7

Ecco come risponderei a questa domanda in una situazione di intervista (non ho mai visto questa domanda prima e non ho guardato le altre risposte fino a quando non ho avuto la mia soluzione):

Innanzitutto, ho provato a capirlo (che hai chiamato la "soluzione matematica") e quando sono arrivato al bicchiere 8 mi sono reso conto che sarebbe stato più duro di quanto sembrasse perché il vetro 5 inizia a traboccare prima del vetro 4. A quel punto I ha deciso di seguire il percorso di ricorsione (solo un FYI, molte domande di intervista di programmazione richiedono ricorsione o induzione per risolvere).

Pensando in modo ricorsivo, il problema diventa molto più semplice: quanta acqua c'è nel bicchiere 8? La metà della quantità che è fuoriuscita dai bicchieri 4 e 5 (fino a quando non è piena). Naturalmente, ciò significa che dobbiamo rispondere a quanto è uscito dagli occhiali 4 e 5, ma risulta che non è neanche troppo difficile. Quanto si è rovesciato dal bicchiere 5? Metà di quanto tuttavia è stato versato dai bicchieri 2 e 3, meno il litro che è rimasto nel bicchiere 5.

Risolvendolo completamente (e in modo disordinato) si ottiene:

#include <iostream>
#include <cmath>
using namespace std;

double howMuchSpilledOutOf(int liters, int bucketId) {
    double spilledInto = 0.0;
    switch (bucketId) {
        case 1:
            spilledInto = liters; break;
        case 2:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 3:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 4:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 2); break;
        case 5:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 2) + 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 6:
            spilledInto = 0.5 * howMuchSpilledOutOf(liters, 3); break;
        default:
            cerr << "Invalid spill bucket ID " << bucketId << endl;
    }
    return max(0.0, spilledInto - 1.0);
}

double getWaterInBucket(int liters, int bucketId) {
    double contents = 0.0;
    switch (bucketId) {
        case 1:
            contents = liters; break;
        case 2:
            contents = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 3:
            contents = 0.5 * howMuchSpilledOutOf(liters, 1); break;
        case 4:
            contents = 0.5 * howMuchSpilledOutOf(liters, 2); break;
        case 5:
            contents = 0.5 * howMuchSpilledOutOf(liters, 2) + 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 6:
            contents = 0.5 * howMuchSpilledOutOf(liters, 3); break;
        case 7:
            contents = 0.5 * howMuchSpilledOutOf(liters, 4); break;
        case 8:
            contents = 0.5 * howMuchSpilledOutOf(liters, 4) + 0.5 * howMuchSpilledOutOf(liters, 5); break;
        case 9:
            contents = 0.5 * howMuchSpilledOutOf(liters, 5) + 0.5 * howMuchSpilledOutOf(liters, 6); break;
        case 10:
            contents = 0.5 * howMuchSpilledOutOf(liters, 6); break;
        default:
            cerr << "Invalid contents bucket ID" << bucketId << endl;
    }
    return min(1.0, contents);
}

int main(int argc, char** argv)
{
    if (argc == 3) {
        int liters = atoi(argv[1]);
        int bucket = atoi(argv[2]);
        cout << getWaterInBucket(liters, bucket) << endl;
    }
    return 0;
}

A questo punto (o mentre lo stavo scrivendo), direi all'intervistatore che questa non è la soluzione ideale in produzione: c'è un codice duplicato tra howMuchSpilledOutOf()e getWaterInBucket(); ci dovrebbe essere una posizione centrale che associa i secchi ai loro "alimentatori". Ma in un'intervista, in cui la velocità e l'accuratezza dell'implementazione sono più importanti della velocità di esecuzione e della manutenibilità (se non diversamente indicato), questa soluzione è preferibile. Quindi vorrei offrire a refactoring il codice per essere più vicino a ciò che considero qualità di produzione e lasciare che l'intervistatore decida.

Nota finale: sono sicuro che il mio codice abbia un refuso da qualche parte; Lo direi anche all'intervistatore e direi che mi sentirei più sicuro dopo averlo rifattorizzato o testato l'unità.


6
Questa soluzione è codificata per l'esempio. Aggiungere occhiali significa aggiungere "case" allo switch ... Non credo sia una buona soluzione.
Luigi Massa Gallerano,

2
@LuigiMassaGallerano - va bene in questo caso dato che è una domanda di intervista; non dovrebbe essere una soluzione perfetta. L'intervistatore sta cercando di comprendere meglio il processo di pensiero del candidato. E Tom lo sottolinea già this isn't the ideal solution.

1
Onestamente non lo è. Posso assicurarti che questo scenario non doveva essere codificato. Se ho posto una domanda per un colloquio e ho presentato uno scenario di test in cui l'intervistato ha presentato una soluzione codificata, è meglio che sia pronto a offrire una soluzione generale o che probabilmente non supererà il colloquio.
Rig

5

Pensare a questo come un problema di un albero è un'aringa rossa, è davvero un grafico diretto. Ma dimentica tutto.

Pensa a un bicchiere ovunque sotto quello in alto. Avrà uno o due bicchieri sopra di esso che possono traboccare al suo interno. Con la scelta appropriata del sistema di coordinate (non preoccuparti, vedi la fine) possiamo scrivere una funzione per ottenere gli occhiali "genitore" per ogni dato vetro.

Ora possiamo pensare a un algoritmo per ottenere la quantità di liquido versata in un bicchiere, indipendentemente dal trabocco di quel vetro. La risposta è tuttavia molto liquido viene versato in ciascun genitore meno la quantità immagazzinata in ciascun bicchiere genitore, divisa per 2. Basta sommare quella per tutti i genitori. Scrivendolo come un frammento di pitone del corpo di una funzione amount_poured_into ():

# p is coords of the current glass
amount_in = 0
for pp in parents(p):
    amount_in += max((amount_poured_into(total, pp) - 1.0)/2, 0)

Max () è per garantire che non si ottenga una quantità negativa di overflow.

Abbiamo quasi finito! Scegliamo un sistema di coordinate con 'y' in fondo alla pagina, gli occhiali della prima riga sono 0, la seconda riga è 1, ecc. Le coordinate 'x' hanno uno zero sotto il vetro della riga superiore e la seconda riga ha coordinate x di -1 e +1, terza riga -2, 0, +2 e così via. Il punto importante è che il vetro più a sinistra o più a destra nel livello y avrà abs (x) = y.

Concludendo tutto con Python (2.x), abbiamo:

def parents(p):
    """Get parents of glass at p"""

    (x, y) = p
    py = y - 1          # parent y
    ppx = x + 1         # right parent x
    pmx = x - 1         # left parent x

    if abs(ppx) > py:
        return ((pmx,py),)
    if abs(pmx) > py:
        return ((ppx,py),)
    return ((pmx,py), (ppx,py))

def amount_poured_into(total, p):
    """Amount of fluid poured into glass 'p'"""

    (x, y) = p
    if y == 0:    # ie, is this the top glass?
        return total

    amount_in = 0
    for pp in parents(p):
        amount_in += max((amount_poured_into(total, pp) - 1.0)/2, 0)

    return amount_in

def amount_in(total, p):
    """Amount of fluid left in glass p"""

    return min(amount_poured_into(total, p), 1)

Quindi, per ottenere effettivamente la quantità in un bicchiere in p, usa quantità_in (totale, p).

Non è chiaro dall'OP, ma la parola "non è possibile aggiungere parametri" potrebbe significare che alla domanda originale deve essere data risposta in termini di numeri di vetro mostrati. Questo viene risolto scrivendo una funzione di mappatura dai numeri di vetro dello spettacolo al sistema di coordinate interno usato sopra. È complicato, ma può essere utilizzata una soluzione iterativa o matematica. Una funzione iterativa di facile comprensione:

def p_from_n(n):
    """Get internal coords from glass 'number'"""

    for (y, width) in enumerate(xrange(1, n+1)):
        if n > width:
            n -= width
        else:
            x = -y + 2*(n-1)
            return (x, y)

Ora basta riscrivere la funzione amount_in () sopra per accettare un numero di vetro:

def amount_in(total, n):
    """Amount of fluid left in glass number n"""

    p = p_from_n(n)
    return min(amount_poured_into(total, p), 1)

2

Interessante.

Questo prende l'approccio della simulazione.

private void test() {
  double litres = 6;
  for ( int i = 1; i < 19; i++ ) {
    System.out.println("Water in glass "+i+" = "+getWater(litres, i));
  }
}

private double getWater(double litres, int whichGlass) {
  // Don't need more glasses than that.
  /*
   * NB: My glasses are numbered from 0.
   */
  double[] glasses = new double[whichGlass];
  // Pour the water in.
  pour(litres, glasses, 0);
  // Pull out the glass amount.
  return glasses[whichGlass-1];
}

// Simple non-math calculator for which glass to overflow into.
// Each glass overflows into this one and the one after.
// Only covers up to 10 glasses (0 - 9).
int[] overflowsInto = 
{1, 
 3, 4, 
 6, 7, 8, 
 10, 11, 12, 13, 
 15, 16, 17, 18, 19};

private void pour(double litres, double[] glasses, int which) {
  // Don't care about later glasses.
  if ( which < glasses.length ) {
    // Pour up to 1 litre in this glass.
    glasses[which] += litres;
    // How much overflow.
    double overflow = glasses[which] - 1;
    if ( overflow > 0 ) {
      // Remove the overflow.
      glasses[which] -= overflow;
      // Split between two.
      pour(overflow / 2, glasses, overflowsInto[which]);
      pour(overflow / 2, glasses, overflowsInto[which]+1);
    }
  }
}

che stampa (per 6 litri):

Water in glass 1 = 1.0
Water in glass 2 = 1.0
Water in glass 3 = 1.0
Water in glass 4 = 0.75
Water in glass 5 = 1.0
Water in glass 6 = 0.75
Water in glass 7 = 0.0
Water in glass 8 = 0.25
Water in glass 9 = 0.25
Water in glass 10 = 0.0
...

che sembra essere giusto.


-1

Questa è la funzione binomiale. Il rapporto dell'acqua tra i bicchieri di livello N può essere scoperto usando nCr per ciascun bicchiere nel livello. Inoltre, il numero totale di occhiali prima del livello N è la somma da 1 a (N - 1), una formula per la quale dovresti essere in grado di trovare abbastanza facilmente disponibile. Quindi, dato X, dovresti essere in grado di determinare il suo livello e usare nCr per controllare i rapporti degli occhiali per quel livello, e quindi determinare quanta acqua c'è in X, se ci sono abbastanza litri per scendere comunque a X.

In secondo luogo, la tua idea di utilizzare BTree va bene, è solo che BTree è una variabile interna, non un parametro esterno.

IOW, se hai coperto questa matematica nella tua istruzione (qui nel Regno Unito è insegnata prima dell'università), dovresti essere in grado di risolverlo senza troppi problemi.


1
Non credo sia la funzione binomiale. Raggiunge il terzo livello in proporzioni di 1,2,1 come suggerirebbe la funzione binomiale, ma il vetro centrale si riempie per primo e lo schema si spezza dopo.
Winston Ewert,

Il tempo non fa parte della simulazione e non influirà sui risultati finali.
DeadMG

4
dal momento che il suo liquido modellante si riempie e scorre fuori dagli occhiali, dovrei sostenere che il tempo è implicitamente parte della simulazione. A 5 litri, 4 e 6 saranno pieni a metà e 5 saranno tutti pieni. Quando viene aggiunto il sesto litro, inizierà a versare in 8 e 9, ma 7 e 10 non riceveranno acqua perché 4 e 6 non hanno ancora raggiunto la capacità. Pertanto, la funzione binomiale non prevede i valori corretti.
Winston Ewert,

3
-1, questo è sbagliato. I livelli non saranno riempiti in modo uniforme.
dan_waterworth,

Hai ragione, non l'ho considerato. Ma dopo averci pensato per un po ', mi sono reso conto che avevi ragione. Non sono sicuro di come modificare la formula per tenerne conto.
DeadMG
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.