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

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:

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:

Captura de pantalla-2023-11-19-at-4.18.24-PM
Estilos de visualización para el problema de Two Sum de Leetcode

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.

Screenshot-2023-11-20-at-8.43.08-AM
Visualización del enfoque de fuerza bruta

Para la solución con Map, mostraremos cómo se construye el mapa y se verifica el par que suma el objetivo.

Screenshot-2023-11-20-at-8.51.40-AM
Visualización de la solución con Map

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.

Screenshot-2023-11-20-at-10.02.46-AM

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.

Screenshot-2023-11-20-at-10.42.51-AM
“`

Cómo Deshabilitar el bruteForceSolutionBtn Cuando la Animación Está en Progreso

Si intentas hacer clic en el bruteForceSolutionBtn varias veces seguidas, verás fallas en la animación. Para solucionarlo, debemos deshabilitar el botón cuando la animación esté en ejecución y luego habilitarlo nuevamente cuando la animación esté completa.

Dentro de la función bruteForceApproach, establece el atributo disabled para el bruteForceSolutionBtn.

const bruteForceApproach = async () => {  bruteForceSolutionBtn.setAttribute("disabled", "");

Dentro de la declaración if, elimina el atributo disabled para el bruteForceSolutionBtn.

   if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `Final indices: [${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;   }

Aquí tienes el código completo con la solución:

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 () => {  bruteForceSolutionBtn.setAttribute("disabled", "");  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        .map(          (num, index) =>            `            <span ${getClassName(num)}>            ${testCaseArray[index]}            </span>          `        )        .join("");      bruteForceTextOutput.textContent = `Does the sum of ${currentNum} + ${currentCompliment} equal ${target}? NO!`;      if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `Final indices: [${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;      }    }  }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);

Prueba la visualización nuevamente, y ahora deberías ver que el botón está deshabilitado cuando la animación se está ejecutando.

Captura-de-pantalla-2023-11-20-a-las-10.56.36-AM
Imagen que muestra cómo el botón está deshabilitado cuando se muestra la animación

Añadiendo la Visualización de la Solución Mediante Map

Creando las Variables const

Vamos a crear algunas nuevas variables const que representarán el botón de la solución óptima, la salida y los elementos de la tabla para la animación.

const optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");

Aquí tienes la lista completa de declaraciones de variables para ambas visualizaciones:

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 optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");const testCaseArray = [11, 15, 2, 7];const target = 9;let currentNum;let currentCompliment;

Creando la función optimalApproach Async

El primer paso es crear la nueva función optimalApproach que será una función async. Puedes agregar esto debajo de tu función bruteForceApproach.

const optimalApproach = async () => {    };

Al igual que la función bruteForceApproach, queremos deshabilitar el botón cuando la animación comienza para evitar que los usuarios hagan clic múltiples veces y rompan la animación.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");};

Cuando la página se carga por primera vez, la tabla está oculta de forma predeterminada. Queremos mostrar el elemento de la tabla cuando comienza la animación.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";};

Cada vez que ejecutamos esta animación, queremos mostrar mensajes al usuario sobre si hemos encontrado el par correcto o no. Al inicio de la animación, queremos borrar la salida anterior.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";};

Luego, necesitamos crear un objeto map vacío que eventualmente se actualizará con el tiempo en el bucle for.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();};

A continuación, necesitamos crear un bucle for que se encargará de recorrer cada número en la matriz y actualizar la animación con el tiempo.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {      }};

Dentro del bucle, necesitamos agregar la expresión para calcular la diferencia.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];  }};

Luego, necesitamos agregar un setTimeout que retrasará los cambios en el marcado HTML en 2 segundos y ayudará con el efecto de animación.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));  }};

Luego, necesitamos agregar una declaración if para verificar si el map tiene el valor de la diferencia.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {            }  }};

Dentro de la declaración if, necesitamos actualizar el contenido de texto para mostrar el resultado final del array de índices en la pantalla. Usaremos el método get para obtener el valor del índice del map.

 if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`; }

También necesitamos actualizar la salida para mostrar un mensaje que indique que hemos encontrado el par de números que suma hasta el objetivo.

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>      <p>¿La diferencia(${difference}) está en nuestro mapa? SÍ, encontramos ese par de números que suman el objetivo.</p>    `;  }

También necesitamos quitar el atributo desactivado del optimalSolutionBtn y retornar de la función.

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>      <p>¿La diferencia(${difference}) está en nuestro mapa? SÍ, encontramos ese par de números que suman el objetivo.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;  }

Si no se ha encontrado el par, entonces necesitamos agregar una cláusula else.

  if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>      <p>¿La diferencia(${difference}) está en nuestro mapa? SÍ, encontramos ese par de números que suman el objetivo.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;  } else {          }

Dentro de la cláusula else, necesitamos actualizar el mensaje para mostrar que no hemos encontrado el par y que el número actual testCaseArray[i] se va a agregar al map junto con su valor de índice.

else {      currentValueOutput.innerHTML = `        <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>        <p>¿La diferencia(${difference}) está en nuestro mapa? No.</p>        <p>Agregar el número actual ${testCaseArray[i]} a nuestro mapa.</p>      `;}

Luego, necesitamos actualizar la salida de la tabla con el número actual y su valor de índice.

else {      currentValueOutput.innerHTML = `        <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>        <p>¿La diferencia(${difference}) está en nuestro mapa? No.</p>        <p>Agregar el número actual ${testCaseArray[i]} a nuestro mapa.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;}

Por último, utiliza el método set para establecer el número actual y su valor de índice en el map.

else {      currentValueOutput.innerHTML = `        <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>        <p>¿La diferencia(${difference}) está en nuestro mapa? No.</p>        <p>Agregar el número actual ${testCaseArray[i]} a nuestro mapa.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;      map.set(testCaseArray[i], i);}

Aquí está el código completo para tu función optimalApproach.

const optimalApproach = async () => {  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>      <p>¿La diferencia(${difference}) está en nuestro mapa? SÍ, encontramos ese par de números que suman el objetivo.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;    } else {      currentValueOutput.innerHTML = `        <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>        <p>¿La diferencia(${difference}) está en nuestro mapa? No.</p>        <p>Agregar el número actual ${testCaseArray[i]} a nuestro mapa.</p>      `;      tableBodyOutput.innerHTML += `      <tr>        <td>${testCaseArray[i]}</td>        <td>${i}</td>      </tr>    `;      map.set(testCaseArray[i], i);    }  }};

Para probar esta visualización, añade un event listener al optimalSolutionBtn y pasa como argumento el evento "click" y la referencia a la función optimalApproach.

optimalSolutionBtn.addEventListener("click", optimalApproach);

Cuando hagas clic en el botón “Mostrar Visualización” para la solución de mapa, deberías ver la animación, como se muestra a continuación:

Screenshot-2023-11-20-at-12.05.05-PM
Visualización para la solución de mapa

Cómo Restablecer la Salida de la Tabla para la Solución de Mapa

Si intentas ejecutar la animación varias veces, verás que la tabla muestra los resultados de la ejecución anterior.

Screenshot-2023-11-20-at-12.09.35-PM
Tabla mostrando los resultados de la ejecución anterior

Para solucionar esto, podemos restablecer la tabla antes de cada ejecución de la animación. Empieza por crear una función resetTable encima de tu función optimalApproach.

const resetTable = () => {    };const optimalApproach = async () => {    .........

Dentro de esa función, borra la tabla y los resultados de texto finales.

const resetTable = () => {  tableBodyOutput.innerHTML = "";  finalOptimalSolutionResult.textContent = "";};

Llama a la función resetTable dentro de tu función optimalApproach para ver los resultados del restablecimiento de la tabla.

const optimalApproach = async () => {  resetTable();  optimalSolutionBtn.setAttribute("disabled", "");    ...........

Prueba tu animación nuevamente y ahora deberías ver que los resultados de la tabla se restablecen antes de cada nueva ejecución de la animación.

Código de Solución Final y Ejemplo en Vivo

Aquí está el código de solución completo en JavaScript para ambas visualizaciones:

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 optimalSolutionBtn = document.getElementById("optimal-visual-btn");const currentValueOutput = document.getElementById("current-value-output");const finalOptimalSolutionResult = document.getElementById(  "final-optimal-result");const table = document.getElementById("table-output");const tableBodyOutput = document.getElementById("map-table-body");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 () => {  bruteForceSolutionBtn.setAttribute("disabled", "");  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        .map(          (num, index) =>            `              <span ${getClassName(num)}>              ${testCaseArray[index]}              </span>            `        )        .join("");      bruteForceTextOutput.textContent = `¿La suma de ${currentNum} + ${currentCompliment} es igual a ${target}? ¡NO!`;      if (currentNum + currentCompliment === target) {        bruteForceTextOutput.textContent = `Índices finales: [${i}, ${j}]`;        bruteForceSolutionBtn.removeAttribute("disabled");        return;      }    }  }};const resetTable = () => {  tableBodyOutput.innerHTML = "";  finalOptimalSolutionResult.textContent = "";};const optimalApproach = async () => {  resetTable();  optimalSolutionBtn.setAttribute("disabled", "");  table.style.display = "block";  currentValueOutput.innerHTML = "";  const map = new Map();  for (let i = 0; i < testCaseArray.length; ++i) {    const difference = target - testCaseArray[i];    await new Promise((resolve) => setTimeout(resolve, 2000));    if (map.has(difference)) {      finalOptimalSolutionResult.textContent = `Índices finales: [${map.get(        difference      )}, ${i}]`;      currentValueOutput.innerHTML = `      <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>      <p>¿La diferencia(${difference}) está en nuestro mapa? SÍ, encontramos ese par de números que suman el objetivo.</p>    `;      optimalSolutionBtn.removeAttribute("disabled");      return;    } else {      currentValueOutput.innerHTML = `        <p>Diferencia(${difference}) = objetivo(${target}) - número actual(${testCaseArray[i]})</p>        <p>¿La diferencia(${difference}) está en nuestro mapa? No.</p>        <p>Agrega el número actual ${testCaseArray[i]} a nuestro mapa.</p>      `;      tableBodyOutput.innerHTML += `        <tr>          <td>${testCaseArray[i]}</td>          <td>${i}</td>        </tr>      `;      map.set(testCaseArray[i], i);    }  }};bruteForceSolutionBtn.addEventListener("click", bruteForceApproach);optimalSolutionBtn.addEventListener("click", optimalApproach);

Aquí está de nuevo un enlace al resultado final en vivo en CodePen.

Conclusión

Espero que hayas disfrutado de ese proyecto y hayas aprendido un poco más sobre cómo funciona el problema de Two Sum.

Te animo a que juegues con el proyecto y tal vez agregues algunas características nuevas por tu cuenta, como probar diferentes números o pedir entradas de usuario para el array de números y los objetivos. 👍🏾


Leave a Reply

Your email address will not be published. Required fields are marked *