🧩 Retos que molan

Estos problemas son más difíciles. No pasa nada si no te salen a la primera. De hecho, es normal. Usa las pistas, piensa, y si te atascas... ¡descansa y vuelve!

🧠
Cómo usar esta sección:
  1. Lee el problema e intenta resolverlo (15-20 min)
  2. Si te atascas, abre la Pista 1
  3. Sigue intentando. Si sigues atascado, abre más pistas
  4. Solo mira la solución cuando hayas agotado las pistas

🎯 Reto 1: El Cifrado Secreto (8 pts)

📜
Enunciado: Tienes un mensaje cifrado. Cada letra se ha desplazado N posiciones en el alfabeto (cifrado César). Dado el mensaje cifrado y el desplazamiento N, devuelve el mensaje original.

Importante: Los espacios y signos de puntuación no cambian. Las mayúsculas siguen siendo mayúsculas.
Entrada:
Krod Pxqgr
3

Salida:
Hola Mundo

Explicación: Cada letra se desplazó 3 posiciones. K→H, r→o, o→l, d→a...

💡 Pista 1: ¿Por dónde empiezo?

Piensa letra por letra. Para cada carácter:

  • Si es letra → desplázala hacia atrás N posiciones
  • Si no es letra → déjala igual

¿Cómo saber si es letra? → .isalpha()

💡 Pista 2: ¿Cómo desplazo una letra?

Las letras tienen un número asociado (código ASCII):

  • ord('A') → 65
  • ord('a') → 97
  • chr(65) → 'A'

Para desplazar: convierte a número, resta N, convierte a letra.

💡 Pista 3: ¿Y si me paso del alfabeto?

Si la letra es 'A' y restas 3, te pasas. Necesitas "dar la vuelta" al alfabeto.

El truco: usa módulo 26 (hay 26 letras).

posicion = (ord(letra) - ord('A') - n) % 26
nueva_letra = chr(posicion + ord('A'))
💡 Pista 4: Mayúsculas y minúsculas

Usa .isupper() para saber si es mayúscula.

Si es mayúscula, usa ord('A') como base.

Si es minúscula, usa ord('a') como base.

🍌 Ver solución Minion (paso a paso)
# Leer el mensaje cifrado y el desplazamiento
mensaje = input()
n = int(input())

# Crear el alfabeto para referencia
alfabeto_mayus = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
alfabeto_minus = "abcdefghijklmnopqrstuvwxyz"

resultado = ""

# Procesar cada carácter
for letra in mensaje:
    if letra in alfabeto_mayus:
        # Es mayúscula
        posicion_actual = alfabeto_mayus.index(letra)
        nueva_posicion = (posicion_actual - n) % 26
        nueva_letra = alfabeto_mayus[nueva_posicion]
        resultado = resultado + nueva_letra
    elif letra in alfabeto_minus:
        # Es minúscula
        posicion_actual = alfabeto_minus.index(letra)
        nueva_posicion = (posicion_actual - n) % 26
        nueva_letra = alfabeto_minus[nueva_posicion]
        resultado = resultado + nueva_letra
    else:
        # No es letra, dejar igual
        resultado = resultado + letra

print(resultado)
😎 Ver solución Gru (compacta)
mensaje = input()
n = int(input())
resultado = ""
for c in mensaje:
    if c.isalpha():
        base = ord('A') if c.isupper() else ord('a')
        resultado += chr((ord(c) - base - n) % 26 + base)
    else:
        resultado += c
print(resultado)
🎓
Truco Gru: ord() y chr() convierten letra↔número. El % 26 hace la "vuelta" automática.

🎯 Reto 2: El Medallero (10 pts)

📜
Enunciado: Tienes los resultados de unas olimpiadas. Cada línea tiene: País, Oros, Platas, Bronces. Ordena los países por:
  1. Más oros (de mayor a menor)
  2. Si empatan en oros → más platas
  3. Si empatan en platas → más bronces
  4. Si empatan en todo → orden alfabético del país
Entrada:
4
España 3 5 2
Francia 3 5 2
Italia 4 1 1
Alemania 3 4 5

Salida:
Italia
España
Francia
Alemania

Italia tiene más oros. España y Francia empatan en todo, así que van por orden alfabético.

💡 Pista 1: ¿Cómo guardo los datos?

Una lista de tuplas o listas:

paises = []
paises.append(("España", 3, 5, 2))
paises.append(("Francia", 3, 5, 2))
...
💡 Pista 2: ¿Cómo ordeno por varios criterios?

La función sorted() acepta una key que puede devolver una tupla.

Python compara tuplas elemento por elemento:

(3, 5) < (4, 1)  # False, porque 3 < 4
(3, 5) < (3, 6)  # True, empatan en el primero, 5 < 6
💡 Pista 3: Orden descendente en unos, ascendente en otros

Truco: para ordenar de mayor a menor, usa números negativos.

# Ordenar por edad descendente
sorted(personas, key=lambda x: -x.edad)

# Ordenar por edad desc, nombre asc
sorted(personas, key=lambda x: (-x.edad, x.nombre))
🍌 Ver solución Minion (paso a paso)
# Leer número de países
n = int(input())

# Guardar los datos de cada país
paises = []

for i in range(n):
    linea = input()
    partes = linea.split()
    nombre = partes[0]
    oros = int(partes[1])
    platas = int(partes[2])
    bronces = int(partes[3])
    paises.append([nombre, oros, platas, bronces])

# Ordenar con bubble sort (fácil de entender)
for i in range(len(paises)):
    for j in range(i + 1, len(paises)):
        pais_a = paises[i]
        pais_b = paises[j]

        # ¿Hay que intercambiar?
        intercambiar = False

        # Comparar por oros (más es mejor)
        if pais_b[1] > pais_a[1]:
            intercambiar = True
        elif pais_b[1] == pais_a[1]:
            # Empate en oros, comparar platas
            if pais_b[2] > pais_a[2]:
                intercambiar = True
            elif pais_b[2] == pais_a[2]:
                # Empate en platas, comparar bronces
                if pais_b[3] > pais_a[3]:
                    intercambiar = True
                elif pais_b[3] == pais_a[3]:
                    # Empate total, ordenar por nombre (A antes que Z)
                    if pais_b[0] < pais_a[0]:
                        intercambiar = True

        if intercambiar:
            paises[i] = pais_b
            paises[j] = pais_a

# Imprimir solo los nombres
for pais in paises:
    print(pais[0])
😎 Ver solución Gru (compacta)
n = int(input())
paises = []
for _ in range(n):
    p = input().split()
    paises.append((p[0], int(p[1]), int(p[2]), int(p[3])))

for pais in sorted(paises, key=lambda x: (-x[1], -x[2], -x[3], x[0])):
    print(pais[0])
🎓
Truco Gru: sorted() con tupla de criterios: negativo = descendente. Una línea hace todo el trabajo.

🎯 Reto 3: Paréntesis Balanceados (7 pts)

📜
Enunciado: Dada una expresión con paréntesis (), corchetes [] y llaves {}, determina si están correctamente balanceados.
Entrada: {[()]}
Salida: SI

Entrada: {[(])}
Salida: NO

Entrada: ((())
Salida: NO
💡 Pista 1: ¿Qué estructura de datos necesito?

Piensa en una pila (stack). Es como una torre de platos:

  • Solo puedes poner platos arriba (push)
  • Solo puedes quitar el de arriba (pop)

En Python, una lista funciona como pila: append() y pop()

💡 Pista 2: ¿Cuál es la lógica?

Recorre la expresión carácter a carácter:

  • Si es (, [ o { → añádelo a la pila
  • Si es ), ] o } → saca el último de la pila y comprueba que coincidan

Al final, la pila debe estar vacía.

💡 Pista 3: ¿Cómo emparejar aperturas y cierres?

Usa un diccionario para saber qué cierre corresponde a cada apertura:

parejas = {'(': ')', '[': ']', '{': '}'}
🍌 Ver solución Minion (paso a paso)
expresion = input()

# Lista que usaremos como pila
pila = []

# Definir qué cierre corresponde a cada apertura
aperturas = "([{"
cierres = ")]}"

# Variable para saber si está balanceado
esta_bien = True

# Recorrer cada carácter
for caracter in expresion:
    # ¿Es una apertura?
    if caracter == "(" or caracter == "[" or caracter == "{":
        pila.append(caracter)

    # ¿Es un cierre?
    elif caracter == ")" or caracter == "]" or caracter == "}":
        # ¿Hay algo en la pila?
        if len(pila) == 0:
            esta_bien = False
            break

        # Sacar el último de la pila
        ultimo = pila.pop()

        # ¿Coinciden?
        if caracter == ")" and ultimo != "(":
            esta_bien = False
            break
        if caracter == "]" and ultimo != "[":
            esta_bien = False
            break
        if caracter == "}" and ultimo != "{":
            esta_bien = False
            break

# Al final, la pila debe estar vacía
if len(pila) > 0:
    esta_bien = False

if esta_bien:
    print("SI")
else:
    print("NO")
😎 Ver solución Gru (compacta)
exp = input()
parejas = {'(':')', '[':']', '{':'}'}
pila = []
ok = True
for c in exp:
    if c in parejas: pila.append(c)
    elif c in ')]}':
        if not pila or parejas[pila.pop()] != c: ok = False; break
print("SI" if ok and not pila else "NO")
🎓
Truco Gru: El diccionario permite emparejar sin múltiples if/elif. pila.pop() saca Y devuelve en una sola operación.

🎯 Reto 4: Números Romanos (12 pts)

📜
Enunciado: Convierte un número romano a decimal.

I=1, V=5, X=10, L=50, C=100, D=500, M=1000

Regla especial: Si un símbolo menor está antes de uno mayor, se resta (IV = 4, IX = 9, XL = 40...).
Entrada: MCMXCIV
Salida: 1994

(M=1000, CM=900, XC=90, IV=4 → 1000+900+90+4 = 1994)
💡 Pista 1: El patrón clave

Recorre de izquierda a derecha. Para cada símbolo:

  • Si el siguiente símbolo es MAYOR → resta el actual
  • Si no → suma el actual
💡 Pista 2: Guarda los valores en un diccionario
valores = {
    'I': 1, 'V': 5, 'X': 10,
    'L': 50, 'C': 100,
    'D': 500, 'M': 1000
}
💡 Pista 3: Cómo mirar el siguiente

Usa un índice y compara con el siguiente:

for i in range(len(romano)):
    actual = valores[romano[i]]
    if i + 1 < len(romano):
        siguiente = valores[romano[i + 1]]
        # comparar...
🍌 Ver solución Minion (paso a paso)
# Leer el número romano
romano = input()

# Diccionario con el valor de cada símbolo
valores = {
    'I': 1,
    'V': 5,
    'X': 10,
    'L': 50,
    'C': 100,
    'D': 500,
    'M': 1000
}

total = 0

# Recorrer cada posición
for i in range(len(romano)):
    simbolo_actual = romano[i]
    valor_actual = valores[simbolo_actual]

    # ¿Hay un símbolo después?
    if i + 1 < len(romano):
        simbolo_siguiente = romano[i + 1]
        valor_siguiente = valores[simbolo_siguiente]

        # Si el siguiente es mayor, hay que restar (ej: IV = 4)
        if valor_siguiente > valor_actual:
            total = total - valor_actual
        else:
            total = total + valor_actual
    else:
        # Es el último, solo sumar
        total = total + valor_actual

print(total)
😎 Ver solución Gru (compacta)
romano = input()
v = {'I':1, 'V':5, 'X':10, 'L':50, 'C':100, 'D':500, 'M':10000
for i in range(len(romano)):
    t += -v[romano[i]] if i+1 < len(romano) and v[romano[i]] < v[romano[i+1]] else v[romano[i]]
print(t)
🎓
Truco Gru: Un ternario dentro del bucle: si el actual es menor que el siguiente, resta; si no, suma.

🎯 Reto 5: Subsecuencia Creciente Más Larga (15 pts)

📜
Enunciado: Dada una lista de números, encuentra la longitud de la subsecuencia creciente más larga.

Una subsecuencia no tiene que ser consecutiva, pero debe mantener el orden original.
Entrada: 10 9 2 5 3 7 101 18
Salida: 4

(La subsecuencia es [2, 3, 7, 101] o [2, 5, 7, 101])
💡 Pista 1: Piensa en cada posición

Para cada número, pregúntate: ¿cuál es la subsecuencia creciente más larga que TERMINA en este número?

Al principio, cada número por sí solo tiene longitud 1.

💡 Pista 2: Mira hacia atrás

Para cada posición i, mira todas las posiciones anteriores j (donde j < i).

Si nums[j] < nums[i], entonces puedes "extender" la subsecuencia que terminaba en j.

💡 Pista 3: Programación dinámica básica

Crea una lista dp donde dp[i] = longitud de la subsecuencia más larga que termina en i.

dp = [1] * n  # Todos empiezan en 1

for i in range(n):
    for j in range(i):
        if nums[j] < nums[i]:
            dp[i] = max(dp[i], dp[j] + 1)
🍌 Ver solución Minion (paso a paso)
# Leer los números
linea = input()
partes = linea.split()
nums = []
for p in partes:
    nums.append(int(p))

n = len(nums)

if n == 0:
    print(0)
else:
    # dp[i] = longitud de la subsecuencia más larga que TERMINA en posición i
    # Al principio, cada número solo tiene longitud 1
    dp = []
    for i in range(n):
        dp.append(1)

    # Para cada posición...
    for i in range(1, n):
        # Miramos todas las posiciones anteriores
        for j in range(i):
            # Si el número en j es menor que en i, podemos extender
            if nums[j] < nums[i]:
                # ¿Es mejor extender desde j que lo que ya tenemos?
                nueva_longitud = dp[j] + 1
                if nueva_longitud > dp[i]:
                    dp[i] = nueva_longitud

    # La respuesta es el máximo de todos los dp
    respuesta = dp[0]
    for valor in dp:
        if valor > respuesta:
            respuesta = valor

    print(respuesta)
😎 Ver solución Gru (compacta)
nums = list(map(int, input().split()))
n = len(nums)
if n == 0: print(0)
else:
    dp = [1] * n
    for i in range(1, n):
        for j in range(i):
            if nums[j] < nums[i]: dp[i] = max(dp[i], dp[j] + 1)
    print(max(dp))
🎓
Truco Gru: Programación dinámica clásica. dp[i] guarda el mejor resultado hasta i. El doble bucle compara con todos los anteriores.

🎯 Reto 6: El Laberinto de Letras (10 pts)

📜
Enunciado: Dada una cuadrícula de letras, encuentra si existe una palabra moviéndote horizontal o verticalmente (sin repetir casillas).
Entrada:
3 4
ABCE
SFCS
ADEE
SEE

Salida: SI

(La palabra SEE se puede formar: S(1,0) → E(2,3) → E(2,2)... ¡espera, hay que repensar!
En realidad: S(1,0) → E(1,3)? No, mejor buscar camino válido)
⚠️
Este es difícil de verdad. Si es tu primer intento con búsqueda en matrices, no te frustres. Es un problema clásico que requiere práctica.
💡 Pista 1: Divide el problema

Dos partes:

  1. Encontrar dónde puede empezar la palabra (todas las casillas con la primera letra)
  2. Desde cada inicio, intentar construir el resto de la palabra
💡 Pista 2: Búsqueda recursiva (backtracking)

Crea una función que reciba: posición actual, índice de la letra que buscamos.

Para cada vecino válido (arriba, abajo, izquierda, derecha), si tiene la letra correcta, llama recursivamente.

Si llegas al final de la palabra → ¡encontrado!

💡 Pista 3: Marcar casillas visitadas

Para no repetir casillas, márcalas temporalmente:

# Marcar como visitada
temp = grid[fila][col]
grid[fila][col] = '#'

# Explorar...

# Desmarcar (backtrack)
grid[fila][col] = temp
🍌 Ver solución Minion (paso a paso)
# Leer dimensiones de la cuadrícula
linea = input().split()
filas = int(linea[0])
cols = int(linea[1])

# Leer la cuadrícula
grid = []
for i in range(filas):
    fila = list(input())  # Convertir string a lista de letras
    grid.append(fila)

# Leer la palabra a buscar
palabra = input()

# Función para buscar desde una posición
def buscar(fila, col, indice_letra):
    # Si ya encontramos toda la palabra, éxito!
    if indice_letra == len(palabra):
        return True

    # ¿Estamos fuera de la cuadrícula?
    if fila < 0 or fila >= filas:
        return False
    if col < 0 or col >= cols:
        return False

    # ¿La letra coincide?
    if grid[fila][col] != palabra[indice_letra]:
        return False

    # Marcar como visitada (para no volver a pasar)
    letra_guardada = grid[fila][col]
    grid[fila][col] = '#'

    # Intentar moverse en las 4 direcciones
    encontrado = False

    # Arriba
    if buscar(fila - 1, col, indice_letra + 1):
        encontrado = True
    # Abajo
    if not encontrado and buscar(fila + 1, col, indice_letra + 1):
        encontrado = True
    # Izquierda
    if not encontrado and buscar(fila, col - 1, indice_letra + 1):
        encontrado = True
    # Derecha
    if not encontrado and buscar(fila, col + 1, indice_letra + 1):
        encontrado = True

    # Desmarcar (backtrack) para probar otros caminos
    grid[fila][col] = letra_guardada

    return encontrado

# Probar desde cada casilla de la cuadrícula
resultado = False
for i in range(filas):
    for j in range(cols):
        if buscar(i, j, 0):
            resultado = True
            break
    if resultado:
        break

if resultado:
    print("SI")
else:
    print("NO")
😎 Ver solución Gru (compacta)
filas, cols = map(int, input().split())
grid = [list(input()) for _ in range(filas)]
palabra = input()

def buscar(f, c, idx):
    if idx == len(palabra): return True
    if f < 0 or f >= filas or c < 0 or c >= cols: return False
    if grid[f][c] != palabra[idx]: return False
    temp, grid[f][c] = grid[f][c], '#'
    ok = buscar(f+1,c,idx+1) or buscar(f-1,c,idx+1) or buscar(f,c+1,idx+1) or buscar(f,c-1,idx+1)
    grid[f][c] = temp
    return ok

print("SI" if any(buscar(i,j,0) for i in range(filas) for j in range(cols)) else "NO")
🎓
Truco Gru: Backtracking con recursión. Marcar → explorar → desmarcar. El any() con generador prueba todas las casillas.
🏆
¡Has llegado al final!

Si has intentado todos estos retos, aunque no los hayas resuelto todos, ya estás por encima del 90% de participantes en cualquier competición.

Cada problema "imposible" que intentas te hace mejor programador. El secreto no es ser un genio, es no rendirse.

🔄
¿Ahora qué? Vuelve a estos retos en una semana. Te sorprenderás de lo fáciles que te parecen. Así funciona el aprendizaje: práctica → descanso → repetición.