algorithm - geeksforgeeks - dynamic programming problems
número mínimo de pasos para reducir el número a 1 (4)
Me gusta la idea por ossifrage aprensivo de mirar con avidez (para el caso de números impares) si n + 1 o n - 1 parece más prometedor, pero creo que decidir lo que parece más prometedor se puede hacer un poco mejor que mirar el número total de establecer bits
Para un número x
,
bin(x)[:: -1].index(''1'')
indica el número de ceros menos significativos hasta el primero 1. La idea, entonces, es ver si este número es más alto para n + 1 o n - 1 , y elegir el más alto de los dos (muchos ceros consecutivos menos significativos indican más a la mitad consecutiva).
Esto lleva a
def min_steps_back(n):
count_to_1 = lambda x: bin(x)[:: -1].index(''1'')
if n in [0, 1]:
return 1 - n
if n % 2 == 0:
return 1 + min_steps_back(n / 2)
return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))
Para comparar los dos, corrí
num = 10000
ms, msb = 0., 0.
for i in range(1000):
n = random.randint(1, 99999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999)
ms += min_steps(n)
msb += min_steps_back(n)
print ms / num, msb / num
Que salidas
57.4797 56.5844
mostrando que, en promedio, esto usa menos operaciones (aunque no por mucho).
Dado cualquier número n, y tres operaciones en n:
- agregar 1
- restar 1
- divide por 2 si el número es par
Quiero encontrar el número mínimo de las operaciones anteriores para reducir n a 1. He intentado el enfoque de programación dinámico, también BFS con poda, pero n puede ser muy grande (10 ^ 300) y no sé cómo hacer mi algoritmo Más rápido. El enfoque codicioso (dividir entre 2 si es par y restar 1 si es impar) tampoco da el resultado óptimo. ¿Existe otra solución?
Hay un patrón que le permite conocer el siguiente paso óptimo en tiempo constante. De hecho, puede haber casos en los que haya dos opciones igualmente óptimas: en ese caso, una de ellas puede derivarse en tiempo constante.
Si observa la representación binaria de ny sus bits menos significativos, puede sacar algunas conclusiones sobre qué operación conduce a la solución. En breve:
- si el bit menos significativo es cero, divida por 2
- si n es 3, o los 2 bits menos significativos son 01, entonces reste
- En todos los demás casos: agregar.
Prueba
Si el bit menos significativo es cero, la siguiente operación debería ser la división por 2. En cambio, podríamos intentar 2 adiciones y luego una división, pero luego se puede lograr el mismo resultado en dos pasos: dividir y agregar. Del mismo modo con 2 restas. Y, por supuesto, podemos ignorar los inútiles pasos posteriores de sumar y restar (o viceversa). Entonces, si el bit final es 0, la división es el camino a seguir.
Entonces los patrones restantes de 3 bits son como **1
. Hay cuatro de ellos. Escribamos a011
para denotar un número que termina con los bits 011
y tiene un conjunto de bits prefijados que representarían el valor a :
a001
: agregar uno daríaa010
, después de lo cual debería ocurrir una división:a01
: 2 pasos tomados. No quisiéramos restar uno ahora, porque eso llevaría aa00
, que podríamos habera00
en dos pasos desde el comienzo (restar 1 y dividir). Así que de nuevo agregamos y dividimos para obtenera1
, y por la misma razón lo repetimos nuevamente, dando:a+1
. Esto tomó 6 pasos, pero conduce a un número al que se podría llegar en 5 pasos (restar 1, dividir 3 veces, agregar 1), así que claramente, no debemos realizar la suma. La resta siempre es mejor.a111
: la suma es igual o mejor que la resta. En 4 pasos obtenemosa+1
. La resta y la división daríaa11
. Agregar ahora sería ineficiente en comparación con la ruta de adición inicial, por lo que repetimos este restar / dividir dos veces y obtenera
en 6 pasos. Sia
termina en 0, entonces podríamos haber hecho esto en 5 pasos (agregar, dividir tres veces, restar), sia
termina en 1, y luego en 4. Entonces, la suma siempre es mejor.a101
: resta y división doble lleva aa1
en 3 pasos. La adición y la división llevan aa11
. Ahora restar y dividir sería ineficiente, en comparación con la ruta de resta, por lo que añadimos y dividimos dos veces para obtenera+1
en 5 pasos. Pero con la ruta de resta, podríamos llegar a esto en 4 pasos. Entonces, la resta siempre es mejor.a011
: suma y doble división lleva aa1
. Para obtenera
tomaría 2 pasos más (5), para obtenera+1
: uno más (4). Resta, división, resta, división doble lleva aa
(5), para obtenera+1
daría un paso más (6). Entonces la adición es al menos tan buena como la resta. Sin embargo, hay un caso que no debe pasarse por alto: si a es 0, entonces la ruta de resta alcanza la solución a la mitad, en 2 pasos, mientras que la ruta de adición toma 3 pasos. Entonces, la adición siempre conduce a la solución, excepto cuando n es 3: luego se debe elegir la resta.
Entonces, para los números impares, el segundo bit determina el siguiente paso (excepto para 3).
Código Python
Esto lleva al siguiente algoritmo (Python), que necesita una iteración para cada paso y, por lo tanto, debe tener una complejidad O (logn) :
def stepCount(n):
count = 0
while n > 1:
if n % 2 == 0: # bitmask: *0
n = n // 2
elif n == 3 or n % 4 == 1: # bitmask: 01
n = n - 1
else: # bitmask: 11
n = n + 1
count += 1
return count
Fragmento de JavaScript
Aquí hay una versión donde puede ingresar un valor para n y dejar que el fragmento produzca el número de pasos:
function stepCount(n) {
var count = 0
while (n > 1) {
if (n % 2 == 0) // bitmask: *0
n = n / 2
else if (n == 3 || n % 4 == 1) // bitmask: 01
n = n - 1
else // bitmask: 11
n = n + 1
count += 1
}
return count
}
// I/O
var input = document.getElementById(''input'')
var output = document.getElementById(''output'')
var calc = document.getElementById(''calc'')
calc.onclick = function () {
var n = +input.value
if (n > 9007199254740991) { // 2^53-1
alert(''Number too large for JavaScript'')
} else {
var res = stepCount(n)
output.textContent = res
}
}
<input id="input" value="123549811245">
<button id="calc">Caluclate steps</button><br>
Result: <span id="output"></span>
Tenga en cuenta que la precisión de JavaScript está limitada a alrededor de 10 16 , por lo que los resultados serán incorrectos para números más grandes. Utilice la secuencia de comandos de Python para obtener resultados precisos.
Para resolver el problema anterior, puede usar recursividad o bucles Ya se proporciona una respuesta recursiva, así que trataría de dar un enfoque de ciclo while.
Lógica : Debemos recordar que el número múltiplo de 2 siempre tendrá menos bits configurados que aquellos que no son divisibles por 2.
Para resolver tu problema, estoy usando el código de Java. Lo he intentado con pocos números y funciona bien, si no agrega un comentario o edita la respuesta
while(n!=1)
{
steps++;
if(n%2 == 0)
{
n=n/2;
}
else
{
if(Integer.bitCount(n-1) > Integer.bitCount(n+1))
{
n += 1;
}
else
{
n -=1;
}
}
}
System.out.println(steps);
El código está escrito de una forma muy simple para que todos puedan entenderlo. Aquí n es el número ingresado y los pasos son los pasos necesarios para alcanzar 1
La solución ofrecida por Ami Tavoy funciona si se considera el 3 (agregar 4 produciría 0b100
y count_to_1
es igual a 2, que es mayor que restar 2 para 0b10
y count_to_1
es igual a 1). Puede agregar dos pasos cuando no baje n = 3 para finalizar la solución:
def min_steps_back(n):
count_to_1 = lambda x: bin(x)[:: -1].index(''1'')
if n in [0, 1]:
return 1 - n
if n == 3:
return 2
if n % 2 == 0:
return 1 + min_steps_back(n / 2)
return 1 + (min_steps_back(n + 1) if count_to_1(n + 1) > count_to_1(n - 1) else min_steps_back(n - 1))
Lo siento, sé que haría un mejor comentario, pero recién comencé.