O "Hacker" no nome do teste sugere que tentemos encontrar uma solução orientada à computação.
Vamos, portanto, começar com um programa para enumeração de força bruta de (a) casos "favoráveis" em que um número inteiro é duas vezes o outro e (b) todos os casos possíveis. A resposta seria então a proporção deles. Eu codifiquei uma solução geral. Sua entrada é um número inteiro positivo n
e sua saída é a probabilidade.
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)
(A prova de correção baseia-se no fato de que para qualquer número positivo .)i≠2ii
Este programa requer testes e até incrementos para cada iteração do loop interno. Portanto, ele precisa entre cálculos de e cada vez que o loop interno é executado, ou a geral. Esse é desempenho de : OK para pequeno como , mas terrível uma vez que excede ou mais.333n6n3n26n2O(n2)nn=100n10000
Como hacker, uma das primeiras coisas que você deseja fazer é eliminar o desempenho quadrático, simplificando o loop interno (se possível). Para isso, passe sistematicamente pelas linhas do loop interno (conforme numerado) e observe o seguinte:
A linha 1 é executada quase uma vez para cada valor de i
e, portanto, all
é incrementada vezes. Consequentemente, para o cálculo de , o loop over pode ser substituído pelo incremento de .n−1all
j
all
n-1
A linha 2 é executada exatamente uma vez quando e, caso contrário, não é. Portanto, ele pode ser substituído pelo incremento de sempre que .2i≤nall
12i≤n
A linha 3 é executada uma vez que i
seja uniforme.
Aqui está o 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 além e eliminar seu loop?
A linha 1 'é executada vezes. Portanto, é incrementado por .nall
n*(n-1)
A linha 2 'é executada apenas quando . Uma maneira de contar isso é (o maior número inteiro menor ou igual a ).⌊ n / 2 ⌋ n / 22i≤n⌊n/2⌋n/2
A linha 3 'é executada apenas para valores pares de . Novamente, isso acontece vezes.⌊ n / 2 ⌋i⌊n/2⌋
A segunda transformação do programa é:
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)
Isso já é uma tremenda conquista: um algoritmo foi reduzido a um algoritmo (que pode ser considerado uma "fórmula fechada" para a resposta).O ( 1 )O(n2)O(1)
Finalmente, existem algumas transformações algébricas simples que podemos fazer rolando a inicialização (linha 0) para o primeiro uso de cada variável e combinando as linhas 2 '' e 3 '':
n=100
all = n * (n-1)
favorable = 2 * floor(n/2)
return(favorable / all)
Nesse ponto, um humano poderia executar o programa. Vamos fazer isso com :n=100
all = 100 * (100-1) = 100*99
favorable = 2 * floor(100/2) = 2*50 = 100
favorable/all = 100 / (100*99) = 1/99
A saída, portanto, é .1/99
Para resumir, um algoritmo de força bruta pode ser transformado sistematicamente usando regras simples de reescrita de programa em um programa elegante e elegante .O(1)