Rompecabezas de la fuente Champaign

30

Se disponen vasos de agua vacíos en el siguiente orden:

ingrese la descripción de la imagen aquí

Cuando vierte líquido en el primer vaso si está lleno, entonces el líquido extra fluirá a los vasos 2 y 3 en cantidades iguales. Cuando el vaso 2 está lleno, el líquido extra fluiría a 4 y 5 y así sucesivamente.

Dado un N litros de líquido y la capacidad máxima de cada vaso es de 1 litro, proporcione la cantidad de líquido presente en cualquier vaso si vacía N litros de líquido vertiéndolo en el vidrio completando la función getWaterInBucket(int N, int X)donde X es el número de vidrio. Entonces, por ejemplo, si quiero tener 4 litros al principio y quiero encontrar el agua en el vaso 3, la función esgetWaterInBucket(4, 3)

¿Cómo resuelvo esto mediante programación? Traté de encontrar una solución matemática usando el triángulo de Pascal. Esto no funcionó. Lo consideré un árbol, así que puedo agregar un parámetro como este getWaterInBucket(BTree root, int N, int X)y luego intentar una solución recursiva para cada nivel, pero los parámetros no están permitidos en este problema. ¿Hay algo obvio, algún truco?

Slartibartfast
fuente
18
Yo no quiero trabajar en una empresa donde los problemas de gestión son sobre fuentes de champán ...
mouviciel
¿Se puede verter en un vaso que no sea el vidrio 1? Si no, cada nivel tendrá cantidades iguales de agua en cada vaso. Por lo tanto, tendrá niveles completos cada vez que vierta 1, 3, 6, 10 ... litros. Si vierte 7 litros, entonces la cuarta fila tiene 4 vasos, por lo que cada uno estará 1/4 lleno. Todos los niveles superiores estarán llenos.
GlenPeterson
55
@GlenPeterson Por lo que estoy leyendo, no creo que se llenen por igual. Sí, 2 y 3 se llenarían por igual porque solo tienen una cosa vertiéndose en ellos, pero una vez que estén llenos, 2 se vierte por igual en 4/5 y 3 se vierte por igual en 5/6, por lo tanto, 5 se llena al doble de la rata de 4/6 . Las copas centrales siempre se llenan más rápido que las copas exteriores. para el 4/6 están llenos 8/9 están llenos 25% y 7/10 todavía están vacíos.
Brad
1
Además, esto me recuerda al Triángulo de Pascal ...
Brad
@mouviciel Haha GlenPeterson: el primer vaso que se vierte siempre es el vaso 1. El entrevistador también dijo que use esta información. Parecía más confundido que yo sobre la respuesta correcta a este problema.
Slartibartfast

Respuestas:

35

Solo necesitas simular el vertido, algo como

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

Tal como está, esto no es un árbol. Debido a que se vierten diferentes vasos en los mismos vasos, eso evita que sea un árbol.

Winston Ewert
fuente
16
+1 por la gran observación de que esto no es un árbol.
Mihai Danila
2
Buena respuesta. En una entrevista, debe decir que esto podría tener problemas de escalabilidad porque calcula el contenido de todas las gafas. Además, debe manejar el estuche donde sale agua de la fila inferior de vasos. Y quieres hacerlo return glasses[N-1], porque los números de cristal comienzan en 1 en lugar de 0.
Tom Panning
1
Creo que la parte desafiante podría ser calcular los índices de los niños izquierdo y derecho. Si presentó esto, el entrevistador solo le pedirá que implemente esas funciones. Puede haber una fórmula explícita.
James
Esa es una solución realmente elegante. Gracias. Le agradecería si pudiera editarlo para agregar comentarios a las líneas de código para explicar lo que cada paso significa en el proceso de pensamiento. Además, el número de gafas no está restringido a 10. Podría ser cualquier cosa
Slartibartfast
1
¿Cómo encuentras los lentes izquierdo y derecho?
kiewic
7

Así es como respondería esta pregunta en una situación de entrevista (no he visto esta pregunta antes, y no miré las otras respuestas hasta que tuve mi solución):

Primero, traté de resolverlo (lo que usted llamó la "solución matemática") y cuando llegué al vidrio 8 me di cuenta de que sería más difícil de lo que parecía porque el vidrio 5 comienza a desbordarse antes que el vidrio 4. En ese punto, decidió seguir la ruta de la recursividad (solo un FYI, muchas preguntas de entrevistas de programación requieren recursividad o inducción para resolverlas).

Pensando recursivamente, el problema se vuelve mucho más fácil: ¿cuánta agua hay en el vaso 8? La mitad de la cantidad que se ha derramado de los vasos 4 y 5 (hasta que esté lleno). Por supuesto, eso significa que tenemos que responder cuánto se ha derramado de los vasos 4 y 5, pero resulta que tampoco es demasiado difícil. ¿Cuánto se ha derramado del vidrio 5? La mitad de lo mucho que se derramó de los vasos 2 y 3, menos el litro que quedó en el vaso 5.

Resolver esto completamente (y desordenadamente) da:

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

En este punto (o mientras escribía esto), le diría al entrevistador que esta no es la solución ideal en producción: hay un código duplicado entre howMuchSpilledOutOf()y getWaterInBucket(); debe haber una ubicación central que asigne los cubos a sus "alimentadores". Pero en una entrevista, donde la velocidad y la precisión de la implementación son más importantes que la velocidad de ejecución y mantenimiento (a menos que se indique lo contrario), esta solución es preferible. Luego, ofrecería refactorizar el código para estar más cerca de lo que considero calidad de producción, y dejar que el entrevistador decida.

Nota final: estoy seguro de que mi código tiene un error tipográfico en alguna parte; También se lo mencionaría al entrevistador y le diría que me sentiría más seguro después de refactorizarlo o probarlo en la unidad.

Toma panorámica
fuente
66
Esta solución está codificada para el ejemplo. Agregar anteojos significa agregar "estuche" al interruptor ... No creo que sea una buena solución.
Luigi Massa Gallerano
2
@LuigiMassaGallerano: está bien en este caso, ya que es una pregunta de entrevista; No se supone que sea una solución perfecta. El entrevistador está tratando de comprender mejor el proceso de pensamiento del candidato. Y Tom ya lo señala this isn't the ideal solution.
1
Honestamente no lo es. Les puedo asegurar que este escenario no estaba destinado a ser codificado. Si hice una pregunta de entrevista y expuse un escenario de caso de prueba en el que el entrevistado presentó una solución codificada, es mejor que esté preparado para ofrecer una solución general o probablemente no pasará la entrevista.
Plataforma
5

Pensando en esto como un problema de árbol es una pista falsa, es realmente un gráfico dirigido. Pero olvídate de eso.

Piense en un vaso en cualquier lugar debajo del superior. Tendrá uno o dos vasos encima que pueden desbordarse. Con la elección adecuada del sistema de coordenadas (no se preocupe, vea el final) podemos escribir una función para obtener las gafas "principales" para cualquier vidrio dado.

Ahora podemos pensar en un algoritmo para obtener la cantidad de líquido vertido en un vaso, independientemente del desbordamiento de ese vaso. Sin embargo, la respuesta es que se vierte mucho líquido en cada padre menos la cantidad almacenada en cada vaso padre, dividido por 2. Simplemente suma eso para todos los padres. Escribiendo esto como un fragmento de Python del cuerpo de una función 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)

El máximo () es para garantizar que no tengamos una cantidad negativa de desbordamiento.

¡Ya casi terminamos! Elegimos un sistema de coordenadas con 'y' hacia abajo de la página, los cristales de la primera fila son 0, la segunda fila es 1, etc. Las coordenadas 'x' tienen un cero debajo del cristal de la fila superior y la segunda fila tiene coordenadas x de -1 y +1, tercera fila -2, 0, +2, y así sucesivamente. El punto importante es que el vidrio más a la izquierda o a la derecha en el nivel y tendrá abs (x) = y.

Envolviendo todo eso en python (2.x), tenemos:

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)

Entonces, para obtener la cantidad realmente en un vaso en p, use cantidad_in (total, p).

No está claro en el OP, pero el bit sobre "no puede agregar parámetros" puede significar que la pregunta original debe responderse en términos de los números de vidrio que se muestran. Esto se resuelve escribiendo una función de mapeo a partir de los números de vidrio de muestra en el sistema de coordenadas interno utilizado anteriormente. Es complicado, pero se puede usar una solución iterativa o matemática. Una función iterativa fácil de entender:

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)

Ahora solo reescriba la función cantidad_in () anterior para aceptar un número de vidrio:

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)
rzzzwilson
fuente
2

Interesante.

Esto toma el enfoque de simulación.

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

que imprime (para 6 litros):

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

que parece ser lo correcto.

OldCurmudgeon
fuente
-1

Esta es la función binomial. La proporción del agua entre los vasos de nivel N se puede descubrir usando nCr para cada vaso en el nivel. Además, el número total de anteojos antes del nivel N es la suma de 1 a (N - 1), una fórmula para la cual debería poder encontrar fácilmente disponible. Por lo tanto, dado X, debería poder determinar su nivel y usar nCr para verificar las proporciones de los vasos para ese nivel, y así determinar cuánta agua hay en X, si de todos modos hay suficientes litros para bajar a X.

En segundo lugar, su idea de usar BTree está bien, es solo que BTree es una variable interna, no un parámetro externo.

IOW, si has cubierto estas matemáticas en tu educación (aquí en el Reino Unido se enseña antes de la universidad), entonces deberías poder resolver esto sin demasiados problemas.

DeadMG
fuente
1
No creo que sea la función binomial. Alcanza el tercer nivel en proporciones de 1,2,1 como sugeriría la función binomial, pero el vaso central se llena primero y el patrón se rompe después de eso.
Winston Ewert
El tiempo no es parte de la simulación y no afectará los resultados finales.
DeadMG
44
Como su líquido de modelado se llena y sale de los vasos, tengo que mantener que el tiempo forma parte implícita de la simulación. Con 5 litros, 4 y 6 estarán medio llenos, y 5 estarán todos llenos. Cuando se agrega el sexto litro, comenzará a verterse en 8 y 9, pero 7 y 10 no recibirán agua porque 4 y 6 aún no han alcanzado la capacidad. Por lo tanto, la función binomial no predecirá los valores correctos.
Winston Ewert
3
-1, esto está mal. Los niveles no se llenarán de manera uniforme.
dan_waterworth
Tienes razón, no lo consideré. Pero después de pensarlo un rato, me di cuenta de que estabas en lo correcto. No estoy seguro de cómo ajustar la fórmula para tener esto en cuenta.
DeadMG