diff --git a/2024/24-verifica-si-los-arboles-son-espejos-magicos/README.md b/2024/24-verifica-si-los-arboles-son-espejos-magicos/README.md new file mode 100644 index 0000000..6176514 --- /dev/null +++ b/2024/24-verifica-si-los-arboles-son-espejos-magicos/README.md @@ -0,0 +1,393 @@ +# Reto 24: Verifica-si-los-arboles-son-espejos-magicos + +En el Polo Norte, los elfos tienen **dos árboles binarios mágicos que generan energía** 🌲🌲 para mantener encendida la estrella navideña ⭐️. Sin embargo, para que funcionen correctamente, los árboles deben estar en perfecta sincronía como espejos 🪞. + +**Dos árboles binarios son espejos si:** + +- Las raíces de ambos árboles tienen el mismo valor. +- Cada nodo del primer árbol debe tener su correspondiente nodo en la posición opuesta en el segundo árbol. + +Y el árbol se representa con tres propiedades `value`, `left` y `right`. Dentro de estas dos últimas va mostrando el resto de ramas (si es que tiene): + +```js +const tree = { + value: '⭐️', + left: { + value: '🎅' + // left: {...} + // right: { ... } + }, + right: { + value: '🎁' + // left: { ... } + // right: { ... } + } +} +``` + +Santa necesita tu ayuda para verificar si los árboles están sincronizados para que la estrella pueda seguir brillando. **Debes devolver un array** donde la **primera posición indica si los árboles están sincronizados** y **la segunda posición devuelve el valor de la raíz del primer árbol**. + +```js +const tree1 = { + value: '🎄', + left: { value: '⭐' }, + right: { value: '🎅' } +} + +const tree2 = { + value: '🎄', + left: { value: '🎅' } + right: { value: '⭐' }, +} + +isTreesSynchronized(tree1, tree2) // [true, '🎄'] + +/* + tree1 tree2 + 🎄 🎄 + / \ / \ +⭐ 🎅 🎅 ⭐ +*/ + +const tree3 = { + value: '🎄', + left: { value: '🎅' }, + right: { value: '🎁' } +} + +isTreesSynchronized(tree1, tree3) // [false, '🎄'] + +const tree4 = { + value: '🎄', + left: { value: '⭐' }, + right: { value: '🎅' } +} + +isTreesSynchronized(tree1, tree4) // [false, '🎄'] + +isTreesSynchronized( + { value: '🎅' }, + { value: '🧑‍🎄' } +) // [false, '🎅'] +``` + +## Mi solución explicada + +```js +function isTreesSynchronized(tree1, tree2) { + function areMirrors(node1, node2) { + if (!node1 && !node2) return true; + if (!node1 || !node2 || node1.value !== node2.value) return false; + + const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); + const rightAndLeftAreMirrors = areMirrors(node1.right, node2.left); + + return leftAndRightAreMirrors && rightAndLeftAreMirrors; + } + + const synchronized = tree1.value === tree2.value && areMirrors(tree1, tree2); + + return [synchronized, tree1.value]; +} +``` + +Para resolver este reto, nuevamente utilizaremos la técnica de recursión. + +Creamos una función `areMirrors` que recibe dos nodos y verifica si son espejos. Dentro tendremos las siguientes condiciones: + +- Si ambos nodos son `null`, devolvemos `true`, ya que no hay nada que comparar. +- Si uno de los dos nodos es `null`, o si los valores de los nodos son diferentes, devolvemos `false`. Ya que no cumplen con las condiciones para ser espejos. + +Luego, comparamos los nodos izquierdos y derechos de ambos árboles. Para esto, llamamos a la función `areMirrors` con los nodos `node1.left` y `node2.right`, y con `node1.right` y `node2.left`. Si ambos son espejos, devolvemos `true`. De lo contrario, devolvemos `false`. + +Seguidamente, comparamos si los árboles son sincronizados. Para esto, verificamos si los valores de las raíces son iguales y si los árboles son espejos. + +Aquí utilizaremos un concepto de JavaScript llamado **short-circuit evaluation**. Si la primera condición es `false`, no se evaluará la segunda condición. Por lo tanto, si los valores de las raíces son diferentes, no se evaluará la función `areMirrors`. + +Finalmente, devolvemos un array con la variable `synchronized` y el valor de la raíz del primer árbol. + +**Veamos con un ejemplo donde opera el short-circuit evaluation:** + +Supongamos que tenemos la siguiente entrada: + +```js +const tree1 = { + value: '🎄', + left: { value: '⭐' }, + right: { value: '🎅' } +} + +const tree2 = { + value: '🎁', + left: { value: '🎅' } + right: { value: '⭐' }, +} +``` + +En este caso, los valores de las raíces son diferentes (`🎄` y `🎁`). Por lo tanto, no se evaluará la función `areMirrors`. + +```js +const synchronized = tree1.value === tree2.value && areMirrors(tree1, tree2); + +// const synchronized = '🎄' === '🎁' && areMirrors(tree1, tree2); +// const synchronized = false && areMirrors(tree1, tree2); +const synchronized = false; +``` + +Como la primera condición es `false`, no se evaluará la segunda condición. Por lo tanto, la función `areMirrors` no se ejecutará. Procemos a devolver `[false, '🎄']`. + +```js +// return [synchronized, tree1.value]; +return [false, '🎄']; +``` + +**Veamos un ejemplo mas complejo**: + +Supongamos que tenemos la siguiente entrada: + +```js +const tree1 = { + value: '🎄', + left: { value: '⭐' }, + right: { value: '🎅' } +} + +const tree2 = { + value: '🎄', + left: { value: '🎅' } + right: { value: '⭐' }, +} +``` + +En este caso, los valores de las raíces son iguales (`🎄`). Por lo tanto, se evaluará la función `areMirrors`. + +```js +const synchronized = tree1.value === tree2.value && areMirrors(tree1, tree2); + +// const synchronized = '🎄' === '🎄' && areMirrors(tree1, tree2 +// const synchronized = true && areMirrors(tree1, tree2); +``` + +Como la primera condición es `true`, se evaluará la segunda condición. En este caso, la función `areMirrors` se ejecutará. + +```js +areMirrors(tree1, tree2); +``` + +Dentro de la función `areMirrors`, se evaluarán los nodos izquierdos y derechos de ambos árboles. + +Primero evaluamos la condición donde si ambos nodos son `null`, devolvemos `true`. + +```js +if (!node1 && !node2) return true; +``` + +Como los nodos no son `null`, pasamos a la siguiente condición. + +```js +// en este momento tenemos los siguientes valores +node1 = { + value: '🎄', + left: { value: '⭐' }, + right: { value: '🎅' } +} + +node2 = { + value: '🎄', + left: { value: '🎅' } + right: { value: '⭐' }, +} +``` + +```js +if (!node1 || !node2 || node1.value !== node2.value) return false; +// if (!node1 || !node2 || '🎄' !== '🎄') return false; +// if (false || false || false) return false; +// if (false) return false; + +// como no se cumple la condición, pasamos a lo siguiente +``` + +Como los nodos no son `null` y los valores son iguales, pasamos a evaluar los nodos izquierdos y derechos de ambos árboles. + +```js +const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); +const rightAndLeftAreMirrors = areMirrors(node1.right, node2.left); +``` + +Enfoquémonos en el primer caso, donde evaluamos los nodos izquierdos. + +```js +const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); + +// const leftAndRightAreMirrors = areMirrors({ value: '⭐' }, { value: '⭐' }); +``` + +Volvemos a entrar a la función `areMirrors` y evaluamos los nodos. + +```js +if (!node1 && !node2) return true; +// if (!{ value: '⭐' } && !{ value: '⭐' }) return true; +// if (false && false) return true; +// if (false) return true; + +// como se cumple la condición pasamos a la siguiente +``` + +Pasamos a la siguiente condición. + +```js +if (!node1 || !node2 || node1.value !== node2.value) return false; +// if (false || false || '⭐' !== '⭐') return false; +// if (false || false || false) return false; +// if (false) return false; + +// como no se cumple la condición, pasamos a lo siguiente +``` + +Como los nodos no son `null` y los valores son iguales, pasamos a evaluar los nodos izquierdos y derechos. + +```js +const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); +const rightAndLeftAreMirrors = areMirrors(node1.right, node2.left); + +// const leftAndRightAreMirrors = areMirrors(null, null); +// const rightAndLeftAreMirrors = areMirrors(null, null); +``` + +Aqui en ambos casos, los nodos son `null`, por lo que devolvemos `true`. + +```js +// llamado de leftAndRightAreMirrors + +if (!node1 && !node2) return true; +// if (!null && !null) return true; +// if (true && true) return true; +return true; +``` + +```js +// llamado de rightAndLeftAreMirrors + +if (!node1 && !node2) return true; +// if (!null && !null) return true; +// if (true && true) return true; +return true; +``` + +Como ambos valores son `true`, devolvemos `true`. + +```js +return leftAndRightAreMirrors && rightAndLeftAreMirrors; +// return true && true; +return true; +``` + +Ahora regresamos al primer llamado donde primero nos enfocamos en el `leftAndRightAreMirrors`. Ya evaluamos este llamado y nos devolvio `true`. + +```js +const leftAndRightAreMirrors = areMirrors({ value: '⭐' }, { value: '⭐' }); +const rightAndLeftAreMirrors = areMirrors({ value: '🎅' }, { value: '🎅' }); +``` + +Ahora evaluamos el segundo llamado. + +```js +const leftAndRightAreMirrors = true; +const rightAndLeftAreMirrors = areMirrors({ value: '🎅' }, { value: '🎅' }); +``` + +Volvemos a entrar a la función `areMirrors` y evaluamos los nodos. + +```js +if (!node1 && !node2) return true; +// if (!{ value: '🎅' } && !{ value: '🎅' }) return true; +// if (false && false) return true; +// if (false) return true; + +// como se cumple la condición, devolvemos true +``` + +Pasamos a la siguiente condición. + +```js +if (!node1 || !node2 || node1.value !== node2.value) return false; +// if (false || false || '🎅' !== '🎅') return false; +// if (false) return false; + +// como no se cumple la condición, pasamos a lo siguiente +``` + +Como los nodos no son `null` y los valos son iguales, pasamos a evaluar los nodos izquierdos y derechos. + +```js +const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); +const rightAndLeftAreMirrors = areMirrors(node1.right, node2.left); + +// const leftAndRightAreMirrors = areMirrors(null, null); +// const rightAndLeftAreMirrors = areMirrors(null, null); +``` + +Aqui en ambos casos, los nodos son `null`, por lo que devolvemos `true`. + +```js +// llamado de leftAndRightAreMirrors + +if (!node1 && !node2) return true; +// if (!null && !null) return true; +// if (true && true) return true; +return true; +``` + +```js +// llamado de rightAndLeftAreMirrors + +if (!node1 && !node2) return true; +// if (!null && !null) return true; +// if (true && true) return true; +return true; +``` + +Como ambos valores son `true`, devolvemos `true`. + +```js +return leftAndRightAreMirrors && rightAndLeftAreMirrors; +// return true && true; +return true; +``` + +Ahora regresamos al primer llamado donde primero nos enfocamos en el `rightAndLeftAreMirrors`. Ya evaluamos este llamado y nos devolvio `true`. + +```js +// const leftAndRightAreMirrors = areMirrors({ value: '⭐' }, { value: '⭐' }); +// const rightAndLeftAreMirrors = areMirrors({ value: '🎅' }, { value: '🎅' }); + +const leftAndRightAreMirrors = true; +const rightAndLeftAreMirrors = true; +``` + +Evaluamos si ambos valores son `true`. + +```js +return leftAndRightAreMirrors && rightAndLeftAreMirrors; +// return true && true; +return true; +``` + +Como ambos valores son `true`, devolvemos `true`. Entonces el resultado de la función `areMirrors` es `true`. + +```js +const synchronized = tree1.value === tree2.value && areMirrors(tree1, tree2); +// const synchronized = '🎄' === '🎄' && true; +// const synchronized = true && true; +const synchronized = true; +``` + +Finalmente, devolvemos `[true, '🎄']`. + +```js +return [synchronized, tree1.value]; +// return [true, '🎄']; +``` + +Y con esto hemos terminado de resolver el reto 🎉. diff --git a/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.js b/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.js new file mode 100644 index 0000000..4200283 --- /dev/null +++ b/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.js @@ -0,0 +1,17 @@ +function isTreesSynchronized(tree1, tree2) { + function areMirrors(node1, node2) { + if (!node1 && !node2) return true; + if (!node1 || !node2 || node1.value !== node2.value) return false; + + const leftAndRightAreMirrors = areMirrors(node1.left, node2.right); + const rightAndLeftAreMirrors = areMirrors(node1.right, node2.left); + + return leftAndRightAreMirrors && rightAndLeftAreMirrors; + } + + const synchronized = tree1.value === tree2.value && areMirrors(tree1, tree2); + + return [synchronized, tree1.value]; +} + +module.exports = isTreesSynchronized; diff --git a/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.test.js b/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.test.js new file mode 100644 index 0000000..0a31990 --- /dev/null +++ b/2024/24-verifica-si-los-arboles-son-espejos-magicos/index.test.js @@ -0,0 +1,50 @@ +const isTreesSynchronized = require('./index'); + +describe('24 => Verifica-si-los-arboles-son-espejos-magicos', () => { + const TEST_CASES = [ + { + input: [{ value: '🎄' }, { value: '🎄' }], + output: [true, '🎄'], + }, + { + input: [ + { value: '🎄', left: { value: '⭐' }, right: { value: '🎅' } }, + { value: '🎄', left: { value: '🎅' }, right: { value: '⭐' } }, + ], + output: [true, '🎄'], + }, + { + input: [ + { value: '✨', left: { value: '⭐' }, right: { value: '🎅' } }, + { value: '✨', left: { value: '🎅' }, right: { value: '🎁' } }, + ], + output: [false, '✨'], + }, + { + input: [{ value: '🎁' }, { value: '🎁' }], + output: [true, '🎁'], + }, + { + input: [{ value: '🎄' }, { value: '🎁' }], + output: [false, '🎄'], + }, + { + input: [ + { value: '🎄', left: { value: '⭐' } }, + { value: '🎄', right: { value: '⭐' } }, + ], + output: [true, '🎄'], + }, + ]; + + it('should return an array', () => { + expect(Array.isArray(isTreesSynchronized({}, {}))).toBe(true); + }); + + it.each(TEST_CASES)( + 'should return the correct value', + ({ input, output }) => { + expect(isTreesSynchronized(...input)[0]).toBe(output[0]); + }, + ); +}); diff --git a/README.md b/README.md index f53c0f4..6688863 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,7 @@ npm run test 'year'/'challenge'/index.test.js | 21 | [🎄 Calcula la altura del árbol de Navidad](https://adventjs.dev/es/challenges/2024/21) | 🟢 | [here](./2024/21-calcula-la-altura-del-arbol-de-navidad/index.js) | [here](./2024/21-calcula-la-altura-del-arbol-de-navidad/README.md#mi-solución-explicada) | ⭐⭐⭐⭐⭐ | | 22 | [🎁 Genera combinaciones de regalos](https://adventjs.dev/es/challenges/2024/22) | 🟡 | [here](./2024/22-genera-combinaciones-de-regalos/index.js) | [here](./2024/22-genera-combinaciones-de-regalos/README.md#mi-solución-explicada) | ⭐⭐⭐⭐⭐ | | 23 | [🔢 Encuentra los números perdidos](https://adventjs.dev/es/challenges/2024/23) | 🟢 | [here](./2024/23-encuentra-los-numeros-perdidos/index.js) | [here](./2024/23-encuentra-los-numeros-perdidos/README.md#mi-solución-explicada) | ⭐⭐⭐⭐⭐ | +| 24 | [🪞 Verifica si los árboles son espejos mágicos](https://adventjs.dev/es/challenges/2024/24) | 🟡 | [here](./2024/24-verifica-si-los-arboles-son-espejos-magicos/index.js) | [here](./2024/24-verifica-si-los-arboles-son-espejos-magicos/README.md#mi-solución-explicada) | ⭐⭐⭐⭐⭐ | Difficulties legend: 🟢 Easy 🟡 Medium 🔴 Hard