Mateo Sanabria Ardila
ISIS1105: Diseño y análisis de algoritmos
PD: Programación dinámica
Un algoritmo basado en programación dinámica resuelve un problema una única vez y lo almacena para utilizarlo mas adelante de ser necesario
Almacenar
Reusar soluciones subproblemas que ayudan a solucionar el problema
Se guarda una tabla con las soluciones de los problemas resueltos hasta el momento.
Si la solución ya esta simplemente se retorna.
Si la solución no esta:
Se llama recurrentemente a los subproblemas
Se calcula la solución
Se guarda en la tabla
Se busca dar una noción de 'tamaño' para los subproblemas.
Basado en el orden dado por el 'tamaño' del subproblema, se resuelve de menos a mas.
Se soluciona el problema 0, luego el 1, luego el 2,...
def fibonacci(n):
if n == 1 or n === 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
def fibonacci(n):
if n == 1 or n === 2:
return 1
else:
return fibonacci(n-1) + fibonacci(n-2)
fibonacci_memo={}
def fibonacci(n):
if(n in fibonacci_memo):
return fibonacci_memo[n]
elif n==1 or n==0:
val = 1
else:
val = fibonacci(n-1) + fibonacci(n-2)
fibonacci_memo[n] = val
return val
fibonacci_memo = {}
def fibonacci(n):
for k in range(0,n):
if k < 2:
f = 1
else:
f = fibonacci_memo[k-1]
+ fibonacci_memo[k-2]
fibonacci_memo[k] = f
return fibonacci_memo[n]
Se puede mejorar para tener complejidad espacial constate y complejidad temporal lineal?
def fibonacci(n):
for k in range(0,n):
if k < 2:
last = 1
last_last = 1
g = 1
else:
g = last + last_last
last, last_last = g, last
return g
Problema: Encontrar la mejor forma de cortar una varilla de longitud n
Una varilla de longitud i tiene asociado un precio de pi.
Basado en los precios se debe encontrar el mejor conjunto de cortes para obtener los máximos ingresos.
longitud | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
---|---|---|---|---|---|---|---|---|
precio | 1 | 5 | 8 | 9 | 10 | 17 | 17 | 20 |
Cortes | - | 1,3 | 2,2 | 3,1 | 1,1,2 | 1,2,1 | 2,1,1 | 1,1,1 |
---|---|---|---|---|---|---|---|---|
valor | 9 | 9 | 10 | 9 | 7 | 7 | 7 | 4 |
De cuantas formas se puede cortar la varilla de longitud n?
Por ejemplo para una varilla de tamaño n se tiene
Tome ri como la mayor ganancia para una varilla de longitud i, no te que:
Note que existen muchos cálculos que se repinten!
Se puede mejorar aun mas!
# No python
def CUT-ROD(p,n):
if n == 0
return 0
q = -infinity
for i in 1 .. n
q = max(q, p(i) + Cut-Rod(p, n-i))
return q
# No python
def TOP-DOWN-CUT-ROD(p,n):
r = []
for i in 1 .. n
r[i] = -infinity
return TOP-DOWN-CUT-ROD-AUX(p,n,r)
def TOP-DOWN-CUT-ROD-AUX(p,n,r):
if r[n] >= 0
return r[n]
if n == 0
q = 0
else
q = -infinity
for i in 1 .. n
q = max(q,p[i] + TOP-DOWN-CUT-ROD-AUX(p,n-i,r))
r[n] = q
return q
# No python
def BOTTON-UP-CUT-ROD(p,n):
r = [0] * n
for j in 1 .. n
q = -infinity
for i in 1 .. j
q = max(q,p[i] + r[j-i])
r[j] = q
return r[n]
Problema: Dado un secuencia de números encontrar la longitud de la subsecuencia creciente mas larga
Problema: Dado un secuencia de números encontrar la longitud de la subsecuencia creciente mas larga
Graficar el problema
Econtrar como dividir los subproblemas
Econtrar relaciones entre los subproblemas
Generalizar la relación del paso anterior
Implementar
¿Antes de hacer DP, como es la solución basada en una estrategia de dividir y conquistar?
Todas las subsecuencias crecientes tienen un comienzo y un final,índices en el arreglo.
Se puede pensar un subproblema como la
subsecuencia creciente mas larga que termina en el índice k, LIS[k].
Que subproblemas se necesitan para solucionar el subproblema LIS[4]?
Revisar el valor de LIS[j] para todos los j que están conectados con el nodo en el índice 4
Así, LIS[4] es el mayor LIS[j] que este relacionado con el elemento en el índice 4 mas 1.
#longest increasing subsequence
def lis(array):
l = len(array)
# Inside LIS we want to record the subproblem LIS[i]
# i.e LIS[i] contains the longest increasing subsequence
# that ends at i. LIS is initialized with all records in 1
# because an unary array has lis 1
LIS = [1] * l
for i in range(1,l):
subproblems = []
# The idea is to test each increasing subsequence ending at i
for k in range(i):
# if the k element keeps the subsequence increasing
# the recorded LIS at k should be store
if array[k] < array[i]:
subproblems.append(LIS[k])
# The LIS at i should be the max of the LIS for the subproblems
# plus one because i is a new element of the sequence.
# Notice tha if subproblems is empty LIS[i] don't change.
# subproblems empty means that there is no lower element that
# array[i] for all j<i
# In this case default means that max([],default=0) = 0
LIS[i] = 1 + max(subproblems,default=0)
#return the max longest increasing subsequence
return max(LIS,default=0)
def lis(array):
l = len(array)
LIS = [1] * l
max_LIS = 0
for i in range(1,l):
max_lis_sub = 0
for k in range(i):
if array[k] < array[i]:
max_lis_sub = max(max_lis_sub,LIS[k])
max_LIS = max(1 + max_lis_sub, max_LIS)
LIS[i] = 1 + max_lis_sub
return max_LIS
Note que es posible mejorar la complejidad espacial
def lis(array):
l = len(array)
LIS = [1] * l
max_LIS = 0
for i in range(1,l):
for j in range(i):
if array[j] < array[i]:
if 1 + LIS[j] > LIS[i]:
LIS[i] = 1 + LIS[j]
Una alternativa que quizas sea mas clara
Algunos ejercicios de LeetCode
70. Climbing Stairs
746. Min Cost Climbing Stairs
416. Partition Equal Subset Sum
198. House Robber
213. House Robber II
Es una forma de cuantificar cuán diferentes son dos strings entre sí.
Se busca el número mínimo de operaciones necesarias para transformar una cadena en la otra.
Se definen tres operaciones:
Sustitución: Remplazar un único carácter en W1 con uno diferente para internar alcanzar W2.
Insertar: Insertar un único carácter en W1 para internar alcanzar W2.
Eliminación: Eliminar un único carácter de W1 para internar alcanzar W2.
W1
W2