0Pricing
Coding Interview Prep · Lección

Notación Big-O desde cero

Comprenda por qué importa el crecimiento asintótico, cómo eliminar constantes y términos de orden inferior, y cómo interpretar Big-O de un vistazo.

Notación Big-O desde cero es una lección gratuita de Coding Interview Prep en CoddyKit. Esta es la lección 1 de 4. Puedes leer la lección completa abajo gratuitamente — luego la practicas en el navegador con un editor de código integrado y un tutor de IA 24/7. Forma parte de la ruta de aprendizaje de Coding Interview Prep, y tu progreso se sincroniza en la web y la app de CoddyKit. El curso de Coding Interview Prep incluye 4 lecciones en total.

¿Por qué medir la eficiencia de los algoritmos?

Dos programas pueden ser correctos y, aun así, uno terminar en un instante mientras el otro tarde horas. La complejidad temporal describe cómo crece el tiempo de ejecución a medida que aumenta la entrada.

# O(n) approach
def find_max_linear(nums):
    m = nums[0]
    for n in nums:
        if n > m: m = n
    return m

# O(n^2) approach (unnecessary double loop)
def find_max_quadratic(nums):
    for i in range(len(nums)):
        is_max = all(nums[i] >= nums[j] for j in range(len(nums)))
        if is_max: return nums[i]

print(find_max_linear([3, 1, 4, 1, 5, 9]))  # 9

Big-O: cota superior asintótica

Big-O describe la cota superior del peor caso sobre el crecimiento del coste. El truco consiste en eliminar las constantes y los términos menores, porque a gran escala solo importa el término dominante. Consulte el código.

# T(n) = 3n^2 + 5n + 100 is O(n^2)
# because the n^2 term dominates for large n

# T(n) = 2n + 1000 is O(n)
# the constant 1000 becomes negligible

# Rule: drop constants and lower-order terms
# 5n^3 + 2n^2 + n + 1  =>  O(n^3)
# 100 * log(n) + n      =>  O(n)
print('O(n^2) example: counting iterations')
n = 1000
count = sum(1 for i in range(n) for j in range(n))
print(count)  # 1_000_000 = n^2

Clases habituales de complejidad

De más rápida a más lenta: O(1), O(log n), O(n), O(n log n), O(n^2), O(2^n), O(n!). Conocerlas le permite elegir el enfoque adecuado antes de escribir una sola línea.

import math

n = 1000
print(f'O(1):       {1}')
print(f'O(log n):   {int(math.log2(n))}')
print(f'O(n):       {n}')
print(f'O(n log n): {int(n * math.log2(n))}')
print(f'O(n^2):     {n**2}')
# O(2^n) for n=1000 is astronomically large
# O(n!) even larger

Eliminar constantes: por qué importa

Ejecutar 5n pasos o 2n pasos es O(n): las constantes dependen del hardware, no del algoritmo. Big-O las omite para que pueda comparar el crecimiento en igualdad de condiciones.

# Both are O(n) — different constants
def count_a(n):
    total = 0
    for i in range(n):   # n ops
        total += 1
    for i in range(n):   # n ops
        total += 1
    return total  # T(n) = 2n  =>  O(n)

def count_b(n):
    total = 0
    for i in range(5 * n):  # 5n ops
        total += 1
    return total  # T(n) = 5n  =>  O(n)

print(count_a(10), count_b(10))  # 20 50

Mejores, promedio y peores casos

Big-O representa el peor caso; Omega, el mejor caso; y Theta, un límite ajustado para ambos. Cuando en una entrevista le preguntan por «la complejidad», casi siempre se refieren al peor caso.

def linear_search(nums, target):
    for i, n in enumerate(nums):
        if n == target:
            return i  # best case: target at index 0 => O(1)
    return -1         # worst case: not found => O(n)

# Best case O(1): target is first element
print(linear_search([5,1,2,3], 5))   # 0

# Worst case O(n): target not in list
print(linear_search([1,2,3,4], 9))   # -1

O(log n): reducir a la mitad el espacio de búsqueda

Un algoritmo es O(log n) cuando reduce la entrada a la mitad en cada paso, como ocurre con la búsqueda binaria. Incluso para mil millones de elementos, solo requiere unos 30 pasos: es increíblemente rápido. Consulte el código.

def binary_search(arr, target):
    lo, hi = 0, len(arr) - 1
    steps = 0
    while lo <= hi:
        steps += 1
        mid = (lo + hi) // 2
        if arr[mid] == target:
            return mid, steps
        elif arr[mid] < target:
            lo = mid + 1
        else:
            hi = mid - 1
    return -1, steps

import math
arr = list(range(1000))
idx, s = binary_search(arr, 999)
print(f'Found at {idx} in {s} steps (log2(1000)~={math.log2(1000):.1f})')

O(n log n): límite inferior de la ordenación

Cualquier algoritmo de ordenación por comparación necesita al menos O(n log n) en el peor caso: es un límite inferior matemático real. Por eso, ordenar y luego recorrer tiene una complejidad total de O(n log n), no de O(n^2). El código muestra merge sort.

# Merge sort: O(n log n)
def merge_sort(arr):
    if len(arr) <= 1:
        return arr
    mid = len(arr) // 2
    left  = merge_sort(arr[:mid])
    right = merge_sort(arr[mid:])
    return merge(left, right)

def merge(a, b):
    res, i, j = [], 0, 0
    while i < len(a) and j < len(b):
        if a[i] <= b[j]: res.append(a[i]); i+=1
        else:             res.append(b[j]); j+=1
    return res + a[i:] + b[j:]

print(merge_sort([5,2,8,1,9,3]))  # [1,2,3,5,8,9]

Complejidad amortizada

El análisis amortizado promedia el coste de muchas operaciones. El método append de Python tiene un coste amortizado de O(1): normalmente es instantáneo, y el coste excepcional de O(n) al redimensionar se distribuye entre todas las operaciones append.

# Dynamic array append is O(1) amortised
import sys

lst = []
capacities = []
for i in range(16):
    lst.append(i)
    capacities.append(sys.getsizeof(lst))

# Size jumps show reallocation events
for i, c in enumerate(capacities):
    if i > 0 and capacities[i] != capacities[i-1]:
        print(f'Realloc at i={i}, new size={c} bytes')

Reconocer la complejidad en el código

Una regla rápida: cuente los bucles. Un bucle es O(n), dos bucles anidados son O(n^2) y un bucle que reduce el rango a la mitad es O(log n). Los recorridos independientes se suman; solo los bucles anidados se multiplican. Consulte el código.

# Two independent passes: O(n) + O(n) = O(n)
def two_passes(nums):
    total = sum(nums)           # O(n)
    mean = total / len(nums)
    diffs = [abs(n - mean) for n in nums]  # O(n)
    return max(diffs)           # O(n)
# Overall: O(n) -- NOT O(n^2)

# Nested loops: O(n) * O(n) = O(n^2)
def all_pairs(nums):
    pairs = []
    for i in range(len(nums)):       # O(n)
        for j in range(i+1, len(nums)): # O(n)
            pairs.append((nums[i], nums[j]))
    return pairs  # O(n^2)

Conceptos básicos de complejidad espacial

La complejidad espacial mide la memoria adicional que utiliza, aparte de la entrada. Invertir una estructura in situ es O(1); utilizar un mapa hash es O(n). Cuando intercambie tiempo por espacio, indique siempre ambos.

# O(1) space: reverse in-place
def reverse_inplace(arr):
    l, r = 0, len(arr) - 1
    while l < r:
        arr[l], arr[r] = arr[r], arr[l]
        l += 1; r -= 1

# O(n) space: create reversed copy
def reverse_copy(arr):
    return arr[::-1]

a = [1, 2, 3, 4, 5]
reverse_inplace(a)
print(a)  # [5, 4, 3, 2, 1]

Hablar de complejidad en entrevistas

Indique siempre la complejidad sin esperar a que se la pregunten: «Esto requiere O(n log n) de tiempo y O(n) de espacio». Después, proponga una opción más rápida. Este hábito demuestra una verdadera experiencia profesional.

# Example of explaining complexity step by step
def two_sum(nums, target):
    # O(n) time: one pass through nums
    # O(n) space: hash map stores up to n elements
    seen = {}  # value -> index
    for i, n in enumerate(nums):
        complement = target - n
        if complement in seen:   # O(1) lookup
            return [seen[complement], i]
        seen[n] = i
    return []

print(two_sum([2, 7, 11, 15], 9))  # [0, 1]

Comprobación rápida

Comprobación rápida: demuestre cuánto ha asimilado sobre Big-O y las clases de complejidad. Solo es una pregunta; puede hacerlo. 🎯

Resumen de la lección

Resumen: Big-O representa el crecimiento en el peor caso omitiendo las constantes; ya conoce las clases desde O(1) hasta O(n!), y sabe que los bucles independientes se suman mientras que los anidados se multiplican.

Preguntas frecuentes

¿La lección «Notación Big-O desde cero» es gratis?

Sí — el texto completo de «Notación Big-O desde cero» es gratis para leer aquí en la web. Para practicarla de forma interactiva (editor de código integrado y tutor de IA 24/7) y desbloquear el resto del curso de Coding Interview Prep, actualiza a CoddyKit PRO. El curso de Coding Interview Prep incluye 4 lecciones en total.

¿Qué aprenderé en «Notación Big-O desde cero»?

Comprenda por qué importa el crecimiento asintótico, cómo eliminar constantes y términos de orden inferior, y cómo interpretar Big-O de un vistazo. Practicas Coding Interview Prep con código real que ejecutas directamente en el navegador, y un tutor de IA 24/7 responde tus preguntas mientras trabajas en la lección.

¿Necesito experiencia previa para empezar Coding Interview Prep?

No se requiere experiencia previa. Coding Interview Prep en CoddyKit está estructurado para principiantes hasta estudiantes avanzados, así que puedes empezar aquí o desde el inicio y avanzar a tu ritmo. Esta es la lección 1 de 4.

¿Cuánto tiempo toma la lección «Notación Big-O desde cero»?

La mayoría de las lecciones de CoddyKit toman alrededor de 5–10 minutos. Cada una es compacta e interactiva, así que avanzas constantemente y retomas exactamente por donde dejaste en la web y la app.

¿Puedo escribir y ejecutar código en esta lección de Coding Interview Prep?

Sí. Cada lección de Coding Interview Prep incluye un editor de código integrado, así que escribes y ejecutas código real directamente en tu navegador y obtienes retroalimentación instantánea de IA — sin configuración local necesaria.

Todas las lecciones de este curso

  1. Notación Big-O desde cero
  2. Análisis de bucles y bucles anidados
  3. Recursión y método del árbol de recursión
  4. Complejidad espacial y compensaciones
← Volver a Coding Interview Prep