Cómo construir una visualización para el problema de la suma de dos en Leetcode proyecto de HTML, CSS y JavaScript
Con el estado actual del mercado laboral, hay muchas personas haciendo LeetCode [https//leetcode.com/] como una forma de prepararse para entrevistas técnicas. Pero a veces sería bueno tener una visualización que muestre los algoritmos detrás de estos problemas. En este tutorial, nosotros
Con el estado actual del mercado laboral, hay muchas personas que se esfuerzan en LeetCode como una forma de prepararse para entrevistas técnicas.
Pero a veces sería agradable si hubiera una visualización que mostrara los algoritmos detrás de estos problemas.
En este tutorial, construiremos una visualización que muestre un par de enfoques para un problema popular de LeetCode llamado Two Sum. Utilizaremos HTML, CSS y JavaScript básicos para desarrollar este proyecto.
Tabla de contenidos
- Requisitos previos
- Configuración del proyecto
- Cómo resolver el problema de Two Sum de LeetCode
- Información general sobre la visualización de Two Sum
- Agregando la visualización de fuerza bruta
- Cómo desactivar el botón bruteForceSolutionBtn cuando la animación está en progreso
- Agregando la visualización de la solución de Map
- Cómo restablecer la salida de la tabla para la solución de Map
- Código de solución final y ejemplo en vivo
- Conclusión
Requisitos previos
Este tutorial asume que tienes conocimientos básicos de HTML, CSS y JavaScript. Si no has completado un curso para principiantes en alguno de esos lenguajes, te sugiero comenzar con estos recursos:
- Certificación de Diseño Web Responsivo de CodesCode
- Certificación de Algoritmos y Estructuras de Datos de JavaScript de CodesCode
Este tutorial también asume que tienes algunos conocimientos básicos de cómo trabajar con un editor de código o un entorno de desarrollo integrado (IDE). Si no es así, te sugiero que consultes estos recursos:
Configuración del proyecto
Eres libre de desarrollar este proyecto en tu editor de código local de elección o mediante un entorno de desarrollo integrado (IDE) o editor en línea como CodePen, CodeSandbox o Replit.
Este proyecto constará de tres archivos: index.html
, index.js
y styles.css
. Dado que este proyecto se centrará principalmente en JavaScript, he proporcionado todo el HTML y CSS en este repositorio de GitHub aquí.
Eres libre de fork y clone el repositorio, o puedes copiar el código encontrado en los archivos HTML y CSS y agregarlo a tu proyecto.
Una vez que hayas configurado el entorno de tu proyecto, debes iniciar el servidor local y ver este resultado en la pantalla:
Cómo resolver el problema Two Sum de LeetCode
Antes de poder elaborar la visualización de este problema, primero debemos comprenderlo y resolverlo.
Descripción
Para este problema, se te dará una lista de números en cualquier orden y un número objetivo. El objetivo es encontrar el par de números que sumen el objetivo y devolver un array de índices para ese par de números.
En este ejemplo, tenemos la siguiente lista y número objetivo:
[2,7,11,15]objetivo: 9
Los números 2 y 7 suman 9, por lo que devolveríamos [0,1]
porque eso representa los índices donde se puede encontrar el par de números en el array.
Para este problema, puedes asumir que habrá al menos dos números o más en el array y solo habrá una solución posible.
Por ejemplo, no puedes tener este input aquí porque no produce una solución, ya que no hay dos números en esa lista que sumen el objetivo.
[1,2,3,4,5]objetivo: 55
Tampoco obtendrás un input con múltiples soluciones. El siguiente input tiene dos respuestas: [0,1]
y [1,2]
, lo cual va en contra de las reglas de este problema.
[3,3,3]objetivo: 6
Enfoque de Fuerza Bruta
El enfoque más intuitivo sería comenzar desde el principio de la lista de números y comparar cada par posible de números hasta encontrar el par que sume al objetivo.
Echemos un vistazo a este ejemplo:
[11, 15, 2, 7]objetivo:9
Podemos comenzar con el primer número de la lista (11) y verificar cada par posible para ver si suman al número objetivo (9).
11 + 15 = 9? NO11 + 2 = 9? NO11 + 7 = 9? NO
Dado que ninguno de esos pares iguala al objetivo (9), pasamos al segundo número de la lista (15) y verificamos todos los pares posibles. No es necesario verificar 11 + 15 porque ya lo verificamos anteriormente.
15 + 2 = 9? NO15 + 7 = 9? NO
Dado que ninguno de esos pares iguala al objetivo (9), pasamos al tercer número de la lista (2) y verificamos todos los pares posibles.
2 + 7 = 9? ¡¡¡SÍ!!!
Ahora, hemos encontrado el par que suma al objetivo, por lo que devolveríamos [2,3]
porque eso representa los índices donde se puede encontrar el par de números en el array.
Solución JavaScript de Fuerza Bruta y Complejidad Temporal
Esta solución utiliza un bucle for
anidado, lo que daría una complejidad temporal de O(n²). El bucle externo se utiliza para obtener el número actual de la lista y el bucle interno se utiliza para verificar si la suma del número actual y otros números en la lista suma al objetivo.
/** * @param {number[]} nums * @param {number} target * @return {number[]} */var twoSum = function (nums, target) { if (nums.length === 2) return [0, 1]; for (let i = 0; i < nums.length; i++) { const currentNum = nums[i]; for (let j = i + 1; j < nums.length; j++) { if (currentNum + nums[j] === target) return [i, j]; } }};
Nota: He añadido una comprobación adicional en mi solución para verificar si el arreglo de entrada tiene solo dos números. En ese caso, devolvemos inmediatamente [0,1]
porque esos son los únicos índices posibles para ese caso de prueba.
if (nums.length === 2) return [0, 1];
Hasta ahora, nuestros arreglos de entrada han sido muy pequeños. Pero si tuviéramos un arreglo de entrada de 100, 500 o más de 1000 números, podría llevar bastante tiempo encontrar el par que se suma al objetivo.
En la siguiente sección, vamos a ver una solución que utiliza el objeto Map
de JavaScript y se ejecuta en tiempo lineal O(n).
Enfoque y solución con Map
En el enfoque de fuerza bruta, comenzamos desde el principio del arreglo y comparamos todas las posibles parejas de números hasta encontrar el par que se suma al objetivo. Pero en este enfoque podemos usar el objeto Map
de JavaScript y un ciclo for
para encontrar ese par.
El objeto Map
de JavaScript es una colección de pares de clave-valor que permite búsquedas rápidas y tiene métodos incorporados como set()
, get()
y has()
.
Vamos a trabajar con el mismo ejemplo de antes:
[11, 15, 2, 7]target:9
Podemos comenzar a recorrer el arreglo y mirar el número actual en la lista que sería nums[i]
. También queremos crear un nuevo objeto map
que estará vacío al principio.
const map = new Map();for(let i=0; i<nums.length; i++){ }
Dentro de nuestro ciclo, necesitamos calcular la diferencia que será el objetivo menos el número actual.
const map = new Map(); for(let i=0; i<nums.length; i++){ const difference = target - nums[i] }
Dado que sabemos que solo puede haber dos números que se sumen al objetivo, podemos verificar si la diferencia está en el map
. Si es así, hemos encontrado el par y podemos devolver los índices. De lo contrario, podemos agregar ese número actual al map
junto con su índice.
const map = new Map(); for(let i=0; i < nums.length; i++) { const difference = target - nums[i] if(map.has(difference)) { return [map.get(difference), i] } else { map.set(nums[i], i) } }
En el siguiente código, el método has()
se utiliza para verificar si la siguiente key
está en el objeto map
. Este método devolverá un booleano de verdadero o falso.
map.has(difference)
El método get()
se usa para devolver ese elemento del map
.
if(map.has(difference)) { return [map.get(difference), i] }
El método set()
agregará o actualizará una entrada en el map
con una clave y valor.
else { map.set(nums[i], i)}
Aquí tienes el código completo para este enfoque:
/** * @param {number[]} nums * @param {number} target * @return {number[]} */var twoSum = function(nums, target) { if(nums.length === 2) return [0,1] const map = new Map(); for (let i = 0; i < nums.length; i++) { const difference = target - nums[i] if (map.has(difference)) { return [map.get(difference), i] } else { map.set(nums[i], i) } } };
Complejidad de tiempo para la solución con Map
Esta solución tendría una complejidad de tiempo lineal O(n). Como solo estamos utilizando un ciclo en lugar de dos, hemos mejorado la complejidad de tiempo y ya no estamos corriendo en tiempo cuadrático O(n²) como lo estábamos antes.
En las próximas secciones, vamos a comenzar a construir las visualizaciones para cada uno de estos enfoques.
Resumen de la Visualización de la Suma de Dos Números
El objetivo de este proyecto es crear visualizaciones tanto para la solución con Map como para la solución de fuerza bruta.
Para la solución de fuerza bruta, mostraremos cómo es caminar a través de cada par de números hasta encontrar el par que suma el objetivo.
Para la solución con Map, mostraremos cómo se construye el mapa y se verifica el par que suma el objetivo.
Agregando la Visualización de Fuerza Bruta
Creando las Variables const
y let
Primero, necesitamos crear variables const
y asignarles el resultado de acceder a los elementos HTML responsables de mostrar el botón de la solución de fuerza bruta y la salida.
const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector( "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector( "#brute-force-output > .result-text");
El siguiente paso es crear variables const
para el conjunto de prueba y el objetivo que se utilizarán en ambas visualizaciones.
const testCaseArray = [11, 15, 2, 7];const target = 9;
Luego, necesitamos crear las variables let
que representarán el número actual que estamos observando en el ciclo externo y el número complementario que estamos observando en el ciclo interno.
let currentNum;let currentCompliment;
Creando la Función getClassName
En nuestra visualización, queremos mostrar al usuario el par de números actual que estamos verificando aplicando bordes de diferentes colores alrededor de ellos. El número actual tendrá un borde verde y el número complementario actual tendrá un borde azul.
Para actualizar dinámicamente estos estilos con el tiempo, necesitamos crear una función que sea responsable de agregar las clases apropiadas al número actual y su complemento.
Primero, comienza por crear una nueva función getClassName
que tome un parámetro num
.
const getClassName = (num) => { };
Dentro de esa función, crea una declaración switch
con el num
como expresión que estamos verificando.
const getClassName = (num) => { switch (num) { }};
El primer case
debería verificar el currentNum
y retornar una cadena para la clase current-num
.
const getClassName = (num) => { switch (num) { case currentNum: return "class='current-num'"; }};
El segundo case
debería verificar el currentCompliment
y retornar una cadena para la clase compliment-num
.
const getClassName = (num) => { switch (num) { case currentNum: return "class='current-num'"; case currentCompliment: return "class='compliment-num'"; }};
Para el caso default
, debería retornar una cadena vacía porque no vamos a aplicar ninguna clase a ese elemento.
const getClassName = (num) => { switch (num) { case currentNum: return "class='current-num'"; case currentCompliment: return "class='compliment-num'"; default: return ""; }};
Creando la función bruteForceApproach
Esta función será responsable de ejecutar la solución de fuerza bruta y mostrar la visualización en la página.
Primero debemos crear la función bruteForceApproach
, que será una función async
.
const bruteForceApproach = async () => {};
Luego, debemos agregar el bucle externo para nuestro array de casos de prueba.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { }};
Dentro del bucle for
, actualizamos currentNum
para asignarle el valor del número actual que estamos viendo en el array de casos de prueba.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; }};
A continuación, creamos el bucle interior for
.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { } }};
Dentro del bucle interior for
, actualizamos el número currentCompliment
y le asignamos el valor de testCaseArray[j]
. Esto representa cada número a la derecha del número actual.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; } }};
A continuación, debemos agregar un setTimeout
que retrasará los cambios visuales realizados en el marcado en un segundo. Esto ayudará a crear el efecto animado de mostrar las diferentes parejas de números.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; await new Promise((resolve) => setTimeout(resolve, 1000)); } }};
Luego, necesitamos actualizar el HTML para la salida. Comenzamos asignando el array de casos de prueba a bruteForceNumbersOutput.innerHTML
.
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j < testCaseArray.length; ++j) { currentCompliment = testCaseArray[j]; await new Promise((resolve) => setTimeout(resolve, 1000)); bruteForceNumbersOutput.innerHTML = testCaseArray; } }};
Luego, queremos usar el método map
en el array para crear un nuevo array de elementos span
que representen cada número en el array junto con los estilos. También debemos encadenar el método join
para eliminar las comas que el método map
agrega al crear el nuevo array.
“`html
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j setTimeout(resolve, 1000)); bruteForceNumbersOutput.innerHTML = testCaseArray .map( (num, index) => `${testCaseArray[index]}` ) .join(""); } }};
Si no encontramos un par que sume el objetivo, queremos mostrar un mensaje al usuario. Actualiza el contenido de texto para bruteForceTextOutput
y asígnale el siguiente mensaje:
const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j setTimeout(resolve, 1000)); bruteForceNumbersOutput.innerHTML = testCaseArray .map( (num, index) => `${testCaseArray[index]}` ) .join(""); bruteForceTextOutput.textContent = `¿La suma de ${currentNum} + ${currentCompliment} es igual a ${target}? ¡NO!`; } }};
La última pieza consiste en agregar una condición que verifique si encontramos el par de números que suman el objetivo. Si es así, entonces podemos mostrar ese array de índices finales y return
de la función.
if (currentNum + currentCompliment === target) { bruteForceTextOutput.textContent = `Índices finales: [${i}, ${j}]`; return; }
Para probar la visualización de fuerza bruta, necesitaremos agregar un event listener al bruteForceSolutionBtn
. El event listener debe escuchar un evento "click"
y debe hacer referencia a la función bruteForceApproach
.
bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);
Este debería ser tu código completo hasta ahora:
const bruteForceSolutionBtn = document.getElementById("brute-force-visual-btn");const bruteForceNumbersOutput = document.querySelector( "#brute-force-output > .numbers-array");const bruteForceTextOutput = document.querySelector( "#brute-force-output > .result-text");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;const getClassName = (num) => { switch (num) { case currentNum: return "class='current-num'"; case currentCompliment: return "class='compliment-num'"; default: return ""; }};const bruteForceApproach = async () => { for (let i = 0; i < testCaseArray.length; ++i) { currentNum = testCaseArray[i]; for (let j = i + 1; j setTimeout(resolve, 1000)); bruteForceNumbersOutput.innerHTML = testCaseArray .map( (num, index) => `${testCaseArray[index]}` ) .join(""); bruteForceTextOutput.textContent = `¿La suma de ${currentNum} + ${currentCompliment} es igual a ${target}? ¡NO!`; if (currentNum + currentCompliment === target) { bruteForceTextOutput.textContent = `Índices finales: [${i}, ${j}]`; return; } } }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);
Prueba la visualización haciendo click en el botón “Mostrar Visualización” para la aproximación de fuerza bruta.
Leave a Reply