Preguntas de Entrevista para Pruebas Automatizadas en Python: Conceptos de Programación

En el ámbito de las pruebas de software automatizadas, las habilidades de programación son esenciales. Las entrevistas técnicas a menudo incluyen ejercicios de codificación que evalúan la lógica, el conocimiento de estructuras de datos fundamentales como listas enlazadas, árboles binarios, y algoritmos como ordenamiento y búsqueda, junto con su complejidad temporal. Además, se enfatizan tres paradigmas clave: hashing, recursión y división y conquista.

Conceptos de Hashing

El hashing se refiere al uso de estructuras de mapeo en Python, como diccionarios y conjuntos, que ofrecen búsqueda eficiente con complejidad O(1), a diferencia de las secuencias como listas que tienen O(n). Esto es útil para deduplicación y optimización de consultas en problemas de listas.

Deduplicación de Listas

Para eliminar duplicados sin mantener el orden, se puede convertir la lista a un conjunto. Si se requiere preservar el orden, se puede emplear un diccionario o un conjunto auxiliar con una lista de resultados.

Ejemplo sin orden específico:

datos = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
unicos = list(set(datos))
print(unicos)

Salida: [1, 2, 3, 4, 5, 6, 9] (el orden puede variar).

Para mantener el orden de inserción:

datos_originales = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
vistos = set()
resultado = []
for elemento in datos_originales:
    if elemento not in vistos:
        vistos.add(elemento)
        resultado.append(elemento)
print(resultado)

En versiones de Python 3.6 y superiores, los diccionarios preservan el orden de inserción, lo que permite una deduplicación ordenada.

Agrupación y Conteo

Para agrupar elementos y contar ocurrencias, se pueden usar comprensiones de lista o módulos como itertools.

mezcla = [1, 'a', 2, 'b', 1, 'a', 3, 'c', 'b']
conjunto = set(mezcla)
conteos = [(item, mezcla.count(item)) for item in conjunto]
conteos.sort(key=lambda x: x[1], reverse=True)
print(conteos)

Top K en Datos Masivos

Para encontrar los K elementos más grandes en grandes volúmenes de datos, se puede usar ordenamiento con deduplicación o bibliotecas como heapq para eficiencia en memoria.

import heapq
import random

def generador_numeros(cantidad):
    for _ in range(cantidad):
        yield random.randint(0, cantidad * 10)

datos_grandes = generador_numeros(10000000)
k = 1000
mayores_k = heapq.nlargest(k, datos_grandes)
print(mayores_k[:5])

Problema de la Suma de Dos

Dada una lista y un objetivo, encontrar índices de dos eelmentos que sumen el objetivo. Usar hashing para reducir la complejidad de O(n^2) a O(n).

lista_num = [1, 5, 3, 7, 8, 2]
objetivo = 10
mapeo = {}
pares = []
for idx, valor in enumerate(lista_num):
    complemento = objetivo - valor
    if complemento in mapeo:
        pares.append((mapeo[complemento], idx))
    mapeo[valor] = idx
print(pares)

Problemas de Recursión

La recursión es una técnica donde una función se llama a sí misma para descomponer problemas en subproblemas más simples. Es común en problemas como factorial, secuencias de Fibonacci y recorridos de árboles.

Factorial

Calcular el factorial de un número entero positivo.

def calcular_factorial(numero):
    if numero <= 1:
        return 1
    return numero * calcular_factorial(numero - 1)

print(calcular_factorial(5))

Secuencia de Fibonacci

Generar números de la serie Fibonacci, donde cada número es la suma de los dos anteriores. Se puede usar memoización para optimizar.

from functools import lru_cache

@lru_cache(maxsize=None)
def fibonacci(n):
    if n <= 2:
        return 1
    return fibonacci(n - 1) + fibonacci(n - 2)

print(fibonacci(10))

Problemas de Salto

Modelar escenarios como un saltador que puede avanzar en pasos fijos. La recursión define la relación entre niveles.

Ejemplo para saltos de 1 o 2 pasos:

def formas_saltar(pasos):
    if pasos <= 2:
        return pasos
    return formas_saltar(pasos - 1) + formas_saltar(pasos - 2)

print(formas_saltar(6))

Ordenamiento Rápido

Implementar quicksort recursivamente, dividiendo la lista alrededor de un pivote.

def ordenamiento_rapido(secuencia):
    if len(secuencia) < 2:
        return secuencia
    pivote = secuencia[0]
    menores, iguales, mayores = [], [pivote], []
    for elem in secuencia[1:]:
        if elem < pivote:
            menores.append(elem)
        elif elem == pivote:
            iguales.append(elem)
        else:
            mayores.append(elem)
    return ordenamiento_rapido(menores) + iguales + ordenamiento_rapido(mayores)

datos_ordenar = [3, 6, 8, 10, 1, 2, 1]
print(ordenamiento_rapido(datos_ordenar))

Búsqueda Binaria

Buscar un valor en una lista ordenada dividiendo repetidamente el intervalo de búsqueda.

def busqueda_binaria(lista, objetivo, inicio=0, fin=None):
    if fin is None:
        fin = len(lista) - 1
    if inicio > fin:
        return -1
    medio = (inicio + fin) // 2
    if lista[medio] == objetivo:
        return medio
    elif lista[medio] < objetivo:
        return busqueda_binaria(lista, objetivo, medio + 1, fin)
    else:
        return busqueda_binaria(lista, objetivo, inicio, medio - 1)

lista_ordenada = [1, 3, 5, 7, 9, 11]
print(busqueda_binaria(lista_ordenada, 7))

Recorridos de Árboles Binarios

Los árboles binarios se representan con nodos que contienen datos y referencias a hijos izquierdo y derecho. Los recorridos en profundidad (preorden, inorden, postorden) usan recursión.

class Nodo:
    def __init__(self, valor, izquierdo=None, derecho=None):
        self.valor = valor
        self.izquierdo = izquierdo
        self.derecho = derecho

def recorrido_preorden(raiz):
    if raiz is None:
        return
    print(raiz.valor)
    recorrido_preorden(raiz.izquierdo)
    recorrido_preorden(raiz.derecho)

# Ejemplo de uso (omitiendo construcción del árbol por brevedad)

Profundidad Máxima de un Árbol

Calcular la altura máxima de un árbol binario usando recursión.

def profundidad_maxima(nodo):
    if nodo is None:
        return 0
    izquierda = profundidad_maxima(nodo.izquierdo)
    derecha = profundidad_maxima(nodo.derecho)
    return max(izquierda, derecha) + 1

Verificación de Árboles Idénticos

Determinar si dos árboles binarios son estructuralmente idénticos y tienen los mismos valores.

def son_iguales(arbol1, arbol2):
    if arbol1 is None and arbol2 is None:
        return True
    if arbol1 is None or arbol2 is None:
        return False
    return (arbol1.valor == arbol2.valor and 
            son_iguales(arbol1.izquierdo, arbol2.izquierdo) and 
            son_iguales(arbol1.derecho, arbol2.derecho))

Verificación de Árbol Balanceado

Un árbol está balanceado si la diferencia de altura entre sus subárboles izquierdo y derecho no excede 1 para todo nodo.

def es_balanceado(nodo):
    if nodo is None:
        return True
    altura_izq = profundidad_maxima(nodo.izquierdo)
    altura_der = profundidad_maxima(nodo.derecho)
    return abs(altura_izq - altura_der) <= 1 and es_balanceado(nodo.izquierdo) and es_balanceado(nodo.derecho)

Otros Temas Comunes

Estadísticas de Cadenas

Contar la frecuencia de caracteres en una cadena.

cadena = "ejemplo de texto"
frecuencias = {}
for caracter in cadena:
    frecuencias[caracter] = frecuencias.get(caracter, 0) + 1
print(frecuencias)

Inversión de Cadenas

Invertir una cadena, posiblemente solo caracteres alfabéticos.

cadena_original = "abc123def"
lista_invertida = []
for i in range(len(cadena_original) - 1, -1, -1):
    if cadena_original[i].isalpha():
        lista_invertida.append(cadena_original[i])
    else:
        lista_invertida.append(cadena_original[i])
cadena_invertida = ''.join(lista_invertida)
print(cadena_invertida)

Validación de Paréntesis

Verificar si los paréntesis en una cadena están correctamente cerrados usando una pila.

def validar_parentesis(expresion):
    pila = []
    pares = {')': '(', ']': '[', '}': '{'}
    for caracter in expresion:
        if caracter in pares.values():
            pila.append(caracter)
        elif caracter in pares:
            if not pila or pila.pop() != pares[caracter]:
                return False
    return len(pila) == 0

prueba = "{[()]}()(]"
print(validar_parentesis(prueba))

Fusión de Listas Ordenadas

Combinar dos listas ordenadas en una sola lista ordenada.

lista1 = [1, 3, 5, 7]
lista2 = [2, 4, 6, 8, 10]
resultado = []
i = j = 0
while i < len(lista1) and j < len(lista2):
    if lista1[i] < lista2[j]:
        resultado.append(lista1[i])
        i += 1
    else:
        resultado.append(lista2[j])
        j += 1
resultado.extend(lista1[i:])
resultado.extend(lista2[j:])
print(resultado)

Implementación de Pila con Colas

Simular el comportamiento LIFO de una pila usando dos colas.

from collections import deque

class PilaConColas:
    def __init__(self):
        self.cola_principal = deque()
        self.cola_auxiliar = deque()
    
    def apilar(self, valor):
        self.cola_principal.append(valor)
    
    def desapilar(self):
        while len(self.cola_principal) > 1:
            self.cola_auxiliar.append(self.cola_principal.popleft())
        elemento = self.cola_principal.popleft()
        self.cola_principal, self.cola_auxiliar = self.cola_auxiliar, self.cola_principal
        return elemento

pila = PilaConColas()
for num in [10, 20, 30]:
    pila.apilar(num)
print(pila.desapilar())

Etiquetas: Python estructuras de datos algoritmos hashing recursion

Publicado el 6-10 01:24