Champaign Fountain Puzzle

30

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

Quando versi del liquido nel 1 ° bicchiere se è pieno, il liquido in eccesso verrebbe trasportato negli occhiali 2 e 3 in uguali quantità. Quando il vetro 2 è pieno, il liquido in eccesso verrebbe portato in 4 e 5 e così via.

Dato un litro di liquido e la capacità massima di ogni bicchiere è di 1 litro, dai la quantità di liquido presente in qualsiasi bicchiere se svuoti N litri di liquido versandolo nel bicchiere compilando la funzione getWaterInBucket(int N, int X) dove X è il numero di vetro. Quindi per esempio se voglio avere 4 litri all'inizio e voglio trovare l'acqua nel vetro 3 la funzione è getWaterInBucket(4, 3)

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

    
posta Slartibartfast 27.01.2013 - 20:24
fonte

5 risposte

35

Hai solo bisogno di simulare il versamento, qualcosa come

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];
}

Così com'è, questo non è un albero. Perché diversi bicchieri si versano negli stessi bicchieri, che impediscono di essere un albero.

    
risposta data 28.01.2013 - 00:40
fonte
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 finché non ho avuto la mia soluzione):

In primo luogo, ho cercato di capirlo (che hai chiamato la "soluzione matematica") e quando sono arrivato al vetro 8 mi sono reso conto che sarebbe stato più difficile di quanto sembrava perché il vetro 5 inizia a traboccare prima del vetro 4. A quel punto ho deciso di percorrere la via della ricorsione (solo una FYI, molte domande di interviste di programmazione richiedono ricorsione o induzione da risolvere).

Pensando in modo ricorsivo, il problema diventa molto più semplice: quanta acqua c'è nel vetro 8? La metà della quantità che è stata versata dai bicchieri 4 e 5 (finché non è piena). Ovviamente, questo significa che dobbiamo rispondere di quanto è uscito dagli occhiali 4 e 5, ma risulta che non è troppo difficile. Quanto è fuoriuscito dal vetro 5? La metà di quanto molto fuoriuscito dagli occhiali 2 e 3, meno il litro che è rimasto nel vetro 5.

Risolvendo completamente (e in modo disordinato):

#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 come stavo scrivendo), direi all'intervistatore che questa non è la soluzione ideale in produzione: c'è un codice duplicato tra howMuchSpilledOutOf() e getWaterInBucket() ; dovrebbe esserci una posizione centrale che mappa i bucket ai loro "alimentatori". Ma in un'intervista, dove la velocità e l'accuratezza dell'implementazione sono più importanti della velocità di esecuzione e della manutenibilità (salvo diversa indicazione), questa soluzione è preferibile. Quindi vorrei offrire al codice di riferimento di essere più vicino a ciò che considero la qualità della produzione, e lasciare decidere l'intervistatore.

Nota finale: sono sicuro che il mio codice abbia un errore di battitura da qualche parte; Lo direi anche all'intervistatore e dire che mi sentirei più fiducioso dopo averlo refactoring o unit-testing.

    
risposta data 28.01.2013 - 02:50
fonte
5

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

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

Ora possiamo pensare ad un algoritmo per ottenere la quantità di liquido versato in un bicchiere, indipendentemente dal trabocco dal vetro. La risposta è comunque che molto liquido viene versato in ciascun genitore meno la quantità immagazzinata in ciascun bicchiere di genitori, divisa per 2. Somma somma per tutti i genitori. Scrivendolo come un frammento di python 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)

Il max () serve a garantire che non si verifichi una quantità negativa di overflow.

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

Comprendendo tutto ciò in 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 la quantità effettivamente in un bicchiere in p, usa amount_in (total, p).

Non è chiaro dall'OP, ma il bit su "non puoi aggiungere parametri" può significare che la domanda originale deve essere risolta in termini di numeri di vetro mostrati. Questo è risolto scrivendo una funzione di mappatura dai numeri del vetrino al sistema di coordinate interno usato sopra. È laborioso, 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)
    
risposta data 28.01.2013 - 01:08
fonte
2

Interessante.

Questo richiede l'approccio di 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 avere ragione.

    
risposta data 28.01.2013 - 14:49
fonte
-1

Questa è la funzione binomiale. Il rapporto dell'acqua tra gli occhiali di livello N può essere scoperto usando nCr per ogni vetro del livello. Inoltre, il numero totale di occhiali prima del livello N è la somma di 1 a (N - 1), una formula per la quale dovresti essere in grado di trovare facilmente disponibile. Quindi, data 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 è in X, se ci sono abbastanza litri per scendere comunque a X.

In secondo luogo, la tua idea di usare il BTree va bene, è solo che il 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à), allora dovresti essere in grado di risolverlo senza troppi problemi.

    
risposta data 27.01.2013 - 20:39
fonte

Leggi altre domande sui tag