Optimizar no es solo micro-ajustar código: consiste en reducir el orden de crecimiento eligiendo estructuras, cortando trabajo innecesario y simplificando la recursión. Estas pautas ayudan a mejorar el Big-O sin perder claridad.
Un doble loop sin dependencias puede reemplazarse por una sola pasada si combinamos condiciones.
# Ineficiente: dos recorridos
def contar_pares(valores):
pares = 0
impares = 0
for valor in valores:
if valor % 2 == 0:
pares += 1
for valor in valores:
if valor % 2 != 0:
impares += 1
return pares + impares
# Mejor: un solo loop O(n)
def contar_pares_una_pasada(valores):
pares = 0
impares = 0
for valor in valores:
if valor % 2 == 0:
pares += 1
else:
impares += 1
return pares + impares
Salir temprano reduce el peor caso si la condición favorable es frecuente.
def contiene(valores, objetivo):
for valor in valores:
if valor == objetivo:
# mejor caso O(1) si esta al inicio
return True
return False # peor caso O(n)
Guardar resultados intermedios evita recomputar valores caros.
# Prefijo de sumas para responder rangos en O(1)
def prefijos(valores):
p = [0]
for valor in valores:
p.append(p[-1] + valor)
return p
def suma_rango(prefijos, l, r): # r exclusivo
return prefijos[r] - prefijos[l]
Calcular los prefijos cuesta O(n); luego cada consulta pasa de O(n) a O(1).
O(n^2).# Fibonacci con memo
def fib(n, memo):
if n <= 1:
return n
if memo[n] != -1:
return memo[n]
memo[n] = fib(n - 1, memo) + fib(n - 2, memo)
return memo[n]
O(log n) en lugar de buscar lineal.O(1) en vez de listas.O(log n)).Copiar buffers grandes o hacer swaps innecesarios multiplica el costo.
def intercambiar(valores, i, j):
if i == j:
return # evita swap trivial
valores[i], valores[j] = valores[j], valores[i]
En estructuras grandes, trabaja con punteros o índices para evitar duplicar memoria.
Problema: contar cuántos pares de elementos iguales hay en un array.
# O(n^2): compara cada par
def contar_pares_cuadratico(valores):
pares = 0
for i in range(len(valores)):
for j in range(i + 1, len(valores)):
if valores[i] == valores[j]:
pares += 1
return pares
# O(n log n): ordenar y agrupar
def contar_pares_ordenando(valores):
valores.sort() # O(n log n)
pares = 0
i = 0
while i < len(valores):
j = i + 1
while j < len(valores) and valores[j] == valores[i]:
j += 1
repeticiones = j - i
pares += (repeticiones * (repeticiones - 1)) // 2
i = j
return pares
Ordenar permite agrupar iguales y contarlos en una sola pasada. El costo dominante pasa a ser el sort (O(n log n)), mejor que el doble loop cuadrático para n grande.