web-dev-qa-db-fra.com

Puzzle de la fontaine de Champaign

Les verres d'eau vides sont disposés dans l'ordre suivant:

enter image description here

Lorsque vous verrez du liquide dans le 1er verre si c'est plein, le liquide supplémentaire serait volé dans les lunettes 2 et 3 en quantités égales. Lorsque le verre 2 est plein, le liquide supplémentaire serait volé dans 4 et 5 et ainsi de suite.

Compte tenu d'un N litres de litres de liquide et de capacité maximale de chaque verre est de 1 litre, donnez la quantité de liquide présent dans n'importe quel verre si vous videz N litres de liquide en versant dans du verre en remplissant la fonction getWaterInBucket(int N, int X) x est le numéro de verre. Donc, par exemple, si je veux avoir 4 litres au début et que je veux trouver l'eau dans le verre 3, la fonction est getWaterInBucket(4, 3)

Comment résoudre ce problème par programme? J'ai essayé de trouver une solution mathématique utilisant le triangle de Pascal. Cela n'a pas fonctionné. Je considérais que c'est un arbre afin que je puisse ajouter un paramètre comme ceci getWaterInBucket(BTree root, int N, int X), puis essayez une solution récursive pour chaque niveau, mais les paramètres ne sont pas autorisés dans ce problème. Y a-t-il quelque chose d'évident, d'un tour?

30
Vrashabh Irde

Vous avez juste besoin de simuler la coulée, quelque chose comme

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

Comme il se trouve, ce n'est pas un arbre. Parce que différentes lunettes coulent dans les mêmes lunettes, cela l'empêche d'être un arbre.

35
Winston Ewert

Voici comment je voudrais répondre à cette question dans une situation d'entrevue (je n'ai pas vu cette question auparavant, et je n'ai pas regardé les autres réponses avant d'avoir ma solution):

Premièrement, j'ai essayé de simplement comprendre (que vous avez appelé la "solution mathématique") et quand je suis arrivé au verre 8, j'ai compris qu'il serait plus difficile qu'il ne semblait que le verre 5 commence à déborder avant le verre 4. À ce moment-là, je a décidé de descendre la route de la récursivité (juste un FYI, de nombreuses questions d'entretien de programmation nécessitent une récursion ou une induction à résoudre).

Pensant récursivement, le problème devient beaucoup plus facile: combien d'eau est en verre 8? La moitié de la quantité qui s'est renversée de lunettes 4 et 5 (jusqu'à ce qu'elle soit pleine). Bien sûr, cela signifie que nous devons répondre à quel point les lunettes 4 et 5 sont renversées, mais il s'avère que ce n'est pas trop difficile non plus. Combien a été renversé de verre 5? La moitié de la moitié de la larve des lunettes 2 et 3, moins le litre qui restait dans le verre 5.

La résolution complète (et messilière) donne:

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

À ce stade (ou comme je l'écrivais), je dirais à l'intervieweur que ce n'est pas la solution idéale en production: il y a du code en double entre howMuchSpilledOutOf() et getWaterInBucket(); Il devrait y avoir un emplacement central qui mesure des godets vers leurs "mangeoires". Mais dans une interview, où la rapidité et la précision de la mise en œuvre sont plus importantes que la vitesse d'exécution et la maintenabilité (sauf indication contraire), cette solution est préférable. Ensuite, j'offrirais au refacteur du code pour se rapprocher de ce que je considère la qualité de la production et de laisser l'intervieweur à décider.

Note finale: Je suis sûr que mon code a une faute de frappe quelque part; Je voudrais également mentionner cela à l'intervieweur et dis-je que je me sentirais plus confiant, après avoir refactorisé l'informatique ou le tester unitaire.

7
Tom Panning

En pensant à cela comme un problème d'arbre est un hareng rouge, c'est vraiment un graphique dirigé. Mais oubliez tout à ce sujet.

Pensez à un verre n'importe où sous le dessus de la tête. Il aura une ou deux lunettes au-dessus de celui qui peut déborder. Avec le choix approprié du système de coordonnées (ne vous inquiétez pas, voir la fin) Nous pouvons écrire une fonction pour obtenir les lunettes "parent" pour tout verre donné.

Nous pouvons maintenant penser à un algorithme pour obtenir la quantité de liquide versé dans un verre, indépendamment du débordement de ce verre. La réponse est toutefois beaucoup liquide dans chaque parent moins la quantité stockée dans chaque verre parent, divisée par 2. Somme simplement que pour tous les parents. Écrire ceci comme un python Fragment du corps d'une fonction montant_pouded_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)

Le max () est de veiller à ne pas avoir de quantité négative de débordement.

Nous sommes presque terminés! Nous choisissons un système de coordonnées avec 'Y' sur la page, les lunettes de première rangée sont 0, la deuxième ligne est de 1, etc. Les coordonnées "X" ont un zéro sous la ligne de rangée supérieure et la deuxième rangée ont x coordonnées de -1 et +1, troisième rangée -2, 0, +2, et ainsi de suite. Le point important est que le verre gauche ou la plus droite au niveau Y aura ABS (x) = y.

Emballez tout ce que possible en python (2.x), nous avons:

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)

Donc, pour obtenir la quantité réellement dans un verre à P, utilisez montant montant_in (total, p).

Il n'est pas clair de l'OP, mais le bit de "vous ne pouvez pas ajouter de paramètres" peut signifier que la question initiale doit être répondue en termes de verre chiffres montré. Ceci est résolu en écrivant une fonction de mappage des numéros de verre show au système de coordonnées interne utilisé ci-dessus. Il est fidèlement, mais une solution itérative ou mathématique peut être utilisée. Une fonction itérative facile à comprendre:

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)

Maintenant, réécrivez la fonction Montant_in () ci-dessus pour accepter un numéro de verre:

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)
5
rzzzwilson

Intéressant.

Cela prend l'approche de la simulation.

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

quelles impressions (pour 6 litres):

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
...

ce qui semble être à peu près bien.

2
OldCurmudgeon