El "Hacker" en el nombre de la prueba sugiere que intentemos encontrar una solución orientada a la informática.
Comencemos por lo tanto con un programa para la enumeración por fuerza bruta de (a) los casos "favorables" en los que un número entero es dos veces el otro y (b) todos los casos posibles. La respuesta sería su relación. He codificado una solución general. Su entrada es un entero positivo n
y su salida es la probabilidad.
n=100
all=favorable=0
for i=1 to n
for j=1 to n
if (i != j) all=all+1 {1}
if (j == 2*i) favorable = favorable+1 {2}
if (i == 2*j) favorable = favorable+1 {3}
return(favorable / all)
(La prueba de corrección se basa en el hecho de que i ≠ 2 i para cualquier número positivo yo.)
Este programa requiere 3 pruebas y hasta 3incrementos para cada iteración del bucle interno. Por lo tanto, necesita entre3 n y 6 n cálculos cada vez que se realiza el bucle interno, o 3norte2 a 6 6norte2en general. Eso esO (norte2) rendimiento: OK para pequeños norte me gusta n = 100pero terrible una vez norte excede 10000 más o menos.
Como hacker, una de las primeras cosas que querrá hacer es eliminar el rendimiento cuadrático simplificando el bucle interno (si es posible). Para este fin, revise sistemáticamente las líneas en el bucle interno (como numeradas) y observe lo siguiente:
La línea 1 se ejecuta todo menos una vez para cada valor de i
y, por all
lo tanto, se incrementan - 1veces. En consecuencia, para el cálculo de all
, el bucle j
puede reemplazarse incrementando all
por n-1
.
La línea 2 se ejecuta exactamente una vez cuando 2 i ≤ ny de lo contrario no del todo. Por lo tanto, se puede reemplazar incrementando all
por1 cuando 2 i ≤ n.
La línea 3 se ejecuta una vez que se proporciona i
es par.
Aquí está el programa transformado.
n=100
all=favorable=0
for i=1 to n
all = all + (n-1) {1'}
if (2*i <= n) favorable = favorable+1 {2'}
if (even(i)) favorable = favorable+1 {3'}
return(favorable / all)
¿Podemos ir más allá y eliminar su ciclo?
La línea 1 'se ejecuta norteveces. Por all
lo tanto, se incrementa en n*(n-1)
.
La línea 2 'se ejecuta solo cuando 2 i ≤ n. Una forma de contar esto es⌊ n / 2 ⌋ (el mayor entero menor o igual que n / 2)
La línea 3 'se ejecuta solo para valores pares de yo. De nuevo, eso pasa⌊ n / 2 ⌋ veces.
La segunda transformación del programa es:
n=100
all=favorable=0 {0}
all = all + n * (n-1) {1''}
favorable = favorable + floor(n/2) {2''}
favorable = favorable + floor(n/2) {3''}
return(favorable / all)
Esto ya es un logro tremendo: unO (norte2) algoritmo se ha reducido a un O ( 1 ) algoritmo (que puede considerarse una "fórmula cerrada" para la respuesta).
Finalmente, hay algunas transformaciones algebraicas simples que podemos hacer al pasar la inicialización (línea 0) al primer uso de cada variable y combinar las líneas 2 '' y 3 '':
n=100
all = n * (n-1)
favorable = 2 * floor(n/2)
return(favorable / all)
En este punto, un humano podría ejecutar el programa. Hagámoslo conn = 100:
all = 100 * (100-1) = 100*99
favorable = 2 * floor(100/2) = 2*50 = 100
favorable/all = 100 / (100*99) = 1/99
La salida por lo tanto es 1 / 99.
Para resumir, un algoritmo de fuerza bruta puede transformarse sistemáticamente usando reglas simples de reescritura de programas en un diseño elegante, elegante, O ( 1 ) programa.