Live Coding

Verificar se um Objeto está Vazio

Como verificar se um objeto JavaScript está vazio?

Explicação: Um objeto está vazio se não possuir nenhuma propriedade enumerável própria. Podemos usar Object.keys(obj) que retorna um array com os nomes das propriedades enumeráveis de um determinado objeto. Se o comprimento deste array for 0, o objeto está vazio.

function isEmpty(obj) { return Object.keys(obj).length === 0; } console.log(isEmpty({})); // true console.log(isEmpty({ a: 1 })); // false

Inverter uma String

Escreva uma função para inverter uma determinada string.

Explicação: A maneira mais simples é converter a string em um array de caracteres, usar o método reverse() embutido para arrays e, em seguida, juntar os caracteres de volta em uma string.

function reverseString(str) { return str.split('').reverse().join(''); } console.log(reverseString('hello')); // 'olleh'

Verificação de Palíndromo

Escreva uma função que verifica se uma determinada string é um palíndromo.

Explicação: Um palíndromo é uma palavra ou frase que se lê da mesma forma de frente para trás e de trás para frente. Podemos verificar isso invertendo a string (ignorando maiúsculas/minúsculas e caracteres não alfanuméricos para uma verificação mais robusta, mas aqui fazemos uma verificação simples) e comparando-a com a original.

function isPalindrome(str) { const reversed = str.split('').reverse().join(''); return str === reversed; } console.log(isPalindrome('racecar')); // true console.log(isPalindrome('hello')); // false

Encontrar o Número Máximo em um Array

Escreva uma função para encontrar o maior número em um array de números.

Explicação: Você pode iterar pelo array, acompanhando o maior número encontrado até agora. Alternativamente, você pode usar a função Math.max() junto com a sintaxe spread (...) para passar elementos do array como argumentos.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // Ou lançar um erro return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Escreva um programa que imprima números de 1 a n. Mas para múltiplos de três, imprima 'Fizz' em vez do número e para os múltiplos de cinco, imprima 'Buzz'. Para números que são múltiplos de três e cinco, imprima 'FizzBuzz'.

Explicação: Este problema clássico testa a lógica básica de loop e condicional. Você precisa iterar de 1 a n e usar o operador de módulo (%) para verificar a divisibilidade por 3 e 5.

function fizzBuzz(n) { for (let i = 1; i <= n; i++) { if (i % 3 === 0 && i % 5 === 0) { console.log('FizzBuzz'); } else if (i % 3 === 0) { console.log('Fizz'); } else if (i % 5 === 0) { console.log('Buzz'); } else { console.log(i); } } } fizzBuzz(15);

Remover Duplicatas de um Array

Escreva uma função que remove elementos duplicados de um array.

Explicação: Uma maneira moderna e concisa de conseguir isso é usando um Set. Sets armazenam apenas valores únicos. Você pode converter o array para um Set e depois de volta para um array.

function removeDuplicates(arr) { return [...new Set(arr)]; } console.log(removeDuplicates([1, 2, 2, 3, 4, 4, 5])); // [1, 2, 3, 4, 5]

Verificação de Anagrama

Escreva uma função para verificar se duas strings são anagramas uma da outra.

Explicação: Anagramas são palavras formadas pela reorganização das letras de outra. Para verificar, podemos limpar, ordenar e comparar as strings. A limpeza envolve a remoção de caracteres não alfanuméricos e a conversão para um caso consistente.

function isAnagram(str1, str2) { const clean = (str) => str.replace(/[^a-z0-9]/gi, '').toLowerCase(); const sorted = (str) => clean(str).split('').sort().join(''); return sorted(str1) === sorted(str2); } console.log(isAnagram('listen', 'silent')); // true console.log(isAnagram('hello', 'world')); // false

Calcular Fatorial

Escreva uma função para calcular o fatorial de um inteiro não negativo.

Explicação: O fatorial (n!) de um número é o produto de todos os inteiros positivos menores ou iguais a n. Podemos calcular isso iterativamente multiplicando os números de 1 até n.

function factorial(n) { if (n < 0) return undefined; // Fatorial não é definido para números negativos if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Somar Todos os Números em um Array

Escreva uma função que retorna a soma de todos os números em um array.

Explicação: Você pode usar o método reduce, que é ideal para acumular um único valor de um array. Ele recebe uma função de callback e um valor inicial (0 para soma).

function sumArray(arr) { return arr.reduce((accumulator, currentValue) => accumulator + currentValue, 0); } console.log(sumArray([1, 2, 3, 4])); // 10

Achatamento de um Array Aninhado

Escreva uma função para achatar um array aninhado. Para simplificar, assuma apenas um nível de aninhamento.

Explicação: Para um único nível de aninhamento, você pode usar Array.prototype.concat() com o operador spread ou o método flat() (ES2019).

function flattenArray(arr) { // Usando flat() (mais simples se ES2019+ for aceitável) // return arr.flat(); // Usando reduce e concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Contar Vogais em uma String

Escreva uma função que conta o número de vogais (a, e, i, o, u) em uma determinada string.

Explicação: Itere pela string (ignorando maiúsculas/minúsculas) e verifique se cada caractere é uma vogal. Mantenha um contador.

function countVowels(str) { const vowels = 'aeiou'; let count = 0; for (let char of str.toLowerCase()) { if (vowels.includes(char)) { count++; } } return count; } console.log(countVowels('Hello World')); // 3

Converter Frase para Título

Escreva uma função que converte uma frase para o formato de título (a primeira letra de cada palavra em maiúscula).

Explicação: Divida a frase em palavras, depois itere por cada palavra, capitalizando a primeira letra e tornando o restante minúsculo. Finalmente, junte as palavras de volta.

function titleCase(str) { return str.toLowerCase().split(' ').map(word => { return word.charAt(0).toUpperCase() + word.slice(1); }).join(' '); } console.log(titleCase('i am a little tea pot')); // 'I Am A Little Tea Pot'

Problema Two Sum

Dado um array de inteiros nums e um inteiro target, retorne os índices dos dois números de forma que eles somem target.

Explicação: Uma abordagem comum é usar um mapa hash (ou um objeto JavaScript) para armazenar números e seus índices à medida que você itera. Para cada número, verifique se target - current_number existe no mapa.

function twoSum(nums, target) { const map = {}; for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (map[complement] !== undefined) { return [map[complement], i]; } map[nums[i]] = i; } return []; // Ou null, ou lançar erro se não houver solução } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Implementar um Contador usando Closures

Crie uma função que retorna uma função de contador. Cada vez que a função retornada é chamada, ela deve incrementar e retornar uma contagem.

Explicação: Isso demonstra closures. A função interna tem acesso à variável count do escopo da função externa, mesmo depois que a função externa terminou de ser executada.

function createCounter() { let count = 0; return function() { count++; return count; }; } const counter1 = createCounter(); console.log(counter1()); // 1 console.log(counter1()); // 2 const counter2 = createCounter(); console.log(counter2()); // 1

Encontrar o Primeiro Caractere Não Repetido

Escreva uma função que encontra o primeiro caractere não repetido em uma string.

Explicação: Você pode usar um mapa hash para contar as frequências dos caracteres. Primeiro, itere pela string para construir o mapa de frequência. Em seguida, itere pela string novamente e retorne o primeiro caractere com uma contagem de 1.

function firstNonRepeatingChar(str) { const charCount = {}; for (const char of str) { charCount[char] = (charCount[char] || 0) + 1; } for (const char of str) { if (charCount[char] === 1) { return char; } } return null; // Ou algum indicador se todos se repetem } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Corte de Array (Array Chunking)

Escreva uma função que divide um array em grupos de um tamanho especificado.

Explicação: Itere pelo array, pegando fatias do tamanho especificado e as adicionando a um novo array. Lide com o caso em que o último pedaço pode ser menor.

function chunkArray(arr, size) { const chunked = []; let index = 0; while (index < arr.length) { chunked.push(arr.slice(index, index + size)); index += size; } return chunked; } console.log(chunkArray([1, 2, 3, 4, 5, 6, 7], 3)); // [[1, 2, 3], [4, 5, 6], [7]]

Verificar se um Número é Primo

Escreva uma função para determinar se um determinado número é um número primo.

Explicação: Um número primo é um número natural maior que 1 que não possui divisores positivos além de 1 e ele mesmo. Itere de 2 até a raiz quadrada do número, verificando a divisibilidade. Lide com casos de borda como 1 e 2.

function isPrime(num) { if (num <= 1) return false; if (num <= 3) return true; if (num % 2 === 0 || num % 3 === 0) return false; for (let i = 5; i * i <= num; i = i + 6) { if (num % i === 0 || num % (i + 2) === 0) return false; } return true; } console.log(isPrime(7)); // true console.log(isPrime(10)); // false

Encontrar a Palavra Mais Longa em uma String

Escreva uma função que encontra a palavra mais longa em uma frase.

Explicação: Divida a string em um array de palavras. Em seguida, itere pelo array, acompanhando a palavra mais longa encontrada até agora (ou seu comprimento).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Limpar palavras se necessário (ex: remover pontuação) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

Implementar `Array.prototype.map`

Implemente sua própria versão da função Array.prototype.map.

Explicação: A função map recebe um callback e cria um novo array aplicando o callback a cada elemento do array original. Sua função deve iterar pelo array e construir o novo array.

function myMap(arr, callback) { const mappedArray = []; for (let i = 0; i < arr.length; i++) { mappedArray.push(callback(arr[i], i, arr)); } return mappedArray; } const numbers = [1, 4, 9]; const roots = myMap(numbers, Math.sqrt); console.log(roots); // [1, 2, 3]

Implementar `Array.prototype.filter`

Implemente sua própria versão da função Array.prototype.filter.

Explicação: A função filter recebe um callback e cria um novo array com todos os elementos que passam no teste implementado pela função de callback fornecida.

function myFilter(arr, callback) { const filteredArray = []; for (let i = 0; i < arr.length; i++) { if (callback(arr[i], i, arr)) { filteredArray.push(arr[i]); } } return filteredArray; } const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present']; const result = myFilter(words, word => word.length > 6); console.log(result); // ['exuberant', 'destruction', 'present']

Implementar `Array.prototype.reduce`

Implemente sua própria versão da função Array.prototype.reduce.

Explicação: A função reduce executa uma função de callback 'redutora' fornecida pelo usuário em cada elemento do array, passando o valor de retorno do cálculo no elemento precedente. O resultado final da execução do redutor em todos os elementos do array é um único valor. Lide com o argumento de valor inicial.

function myReduce(arr, callback, initialValue) { let accumulator = initialValue; let startIndex = 0; if (initialValue === undefined) { if (arr.length === 0) throw new TypeError('Reduce of empty array with no initial value'); accumulator = arr[0]; startIndex = 1; } for (let i = startIndex; i < arr.length; i++) { accumulator = callback(accumulator, arr[i], i, arr); } return accumulator; } const array1 = [1, 2, 3, 4]; const sum = myReduce(array1, (acc, curr) => acc + curr, 0); console.log(sum); // 10

Memorização - Sequência de Fibonacci

Implemente uma função Fibonacci usando memorização para otimizar o desempenho.

Explicação: A sequência de Fibonacci envolve cálculos repetidos. A memorização armazena os resultados de chamadas de função caras e retorna o resultado em cache quando as mesmas entradas ocorrem novamente.

function memoizedFib() { const cache = {}; function fib(n) { if (n in cache) { return cache[n]; } if (n <= 1) { return n; } const result = fib(n - 1) + fib(n - 2); cache[n] = result; return result; } return fib; } const fibonacci = memoizedFib(); console.log(fibonacci(10)); // 55 console.log(fibonacci(40)); // 102334155 (muito mais rápido que o não memorizado)

Verificar Chaves Balanceadas

Escreva uma função para verificar se uma string contendo chaves {}[]() está balanceada.

Explicação: Use uma pilha. Ao encontrar um colchete de abertura, coloque-o na pilha. Ao encontrar um colchete de fechamento, verifique se a pilha está vazia ou se o elemento superior é o colchete de abertura correspondente. Se corresponder, retire da pilha. Se não, ou se a pilha estiver vazia, está desbalanceado. No final, uma pilha vazia significa balanceado.

function isBalanced(str) { const stack = []; const map = { '(': ')', '{': '}', '[': ']' }; for (let char of str) { if (map[char]) { stack.push(char); } else if (Object.values(map).includes(char)) { if (stack.length === 0) return false; const lastOpen = stack.pop(); if (map[lastOpen] !== char) return false; } } return stack.length === 0; } console.log(isBalanced('({[]})')); // true console.log(isBalanced('([)]')); // false

Implementar uma Fila Simples

Implemente uma estrutura de dados de Fila com os métodos enqueue (adicionar ao final) e dequeue (remover do início).

Explicação: Uma fila segue o princípio FIFO (First-In, First-Out). Um array pode ser usado, com push para enqueue e shift para dequeue.

class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { if (this.isEmpty()) return 'Underflow'; return this.items.shift(); } front() { if (this.isEmpty()) return 'No elements in Queue'; return this.items[0]; } isEmpty() { return this.items.length === 0; } } const q = new Queue(); q.enqueue(10); q.enqueue(20); console.log(q.dequeue()); // 10 console.log(q.front()); // 20

Implementar uma Pilha Simples

Implemente uma estrutura de dados de Pilha com os métodos push (adicionar ao topo) e pop (remover do topo).

Explicação: Uma pilha segue o princípio LIFO (Last-In, First-Out). Um array pode ser usado, com os métodos push e pop.

class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { if (this.isEmpty()) return 'Underflow'; return this.items.pop(); } peek() { return this.items[this.items.length - 1]; } isEmpty() { return this.items.length === 0; } } const s = new Stack(); s.push(10); s.push(20); console.log(s.pop()); // 20 console.log(s.peek()); // 10

Encontrar o Número Ausente na Sequência

Dado um array contendo n números distintos tirados de 0, 1, 2, ..., n, encontre o que está faltando no array.

Explicação: A soma dos números de 0 a n pode ser calculada usando a fórmula n*(n+1)/2. A soma real dos elementos do array pode ser calculada. A diferença entre essas duas somas será o número ausente.

function findMissingNumber(nums) { const n = nums.length; const expectedSum = n * (n + 1) / 2; const actualSum = nums.reduce((sum, num) => sum + num, 0); return expectedSum - actualSum; } console.log(findMissingNumber([3, 0, 1])); // 2 console.log(findMissingNumber([9, 6, 4, 2, 3, 5, 7, 0, 1])); // 8

Função Debounce

Implemente uma função debounce. Debouncing garante que uma função não seja chamada novamente até que uma certa quantidade de tempo tenha passado sem que ela seja chamada.

Explicação: Use setTimeout e clearTimeout. Cada vez que a função 'debounced' é chamada, limpe o timeout anterior e defina um novo. A chamada real da função acontece apenas quando o timeout é concluído.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Exemplo de uso: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Chamado após 1 seg (se não for chamado novamente) debouncedHello(); // Reinicia o timer debouncedHello(); // Reinicia o timer, será executado 1 seg após esta chamada.

Função Throttle

Implemente uma função throttle. Throttling garante que uma função seja chamada no máximo uma vez em um intervalo de tempo especificado.

Explicação: Use uma flag para indicar se uma chamada é permitida. Quando chamada, se permitido, execute a função, defina a flag como false e use setTimeout para redefinir a flag após o intervalo.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Exemplo de uso: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Irá registrar no máximo uma vez por segundo.

Função Currying

Escreva uma função que recebe uma função com dois argumentos e retorna uma versão 'curried' dela.

Explicação: Currying transforma uma função com múltiplos argumentos em uma sequência de funções, cada uma recebendo um único argumento. f(a, b) se torna f(a)(b).

function curry(fn) { return function(a) { return function(b) { return fn(a, b); }; }; } function add(a, b) { return a + b; } const curriedAdd = curry(add); const add5 = curriedAdd(5); console.log(add5(3)); // 8 console.log(curriedAdd(10)(20)); // 30

Clonagem Profunda de um Objeto

Escreva uma função para realizar uma clonagem profunda de um objeto JavaScript.

Explicação: Um clone raso copia apenas as propriedades de nível superior. Um clone profundo copia recursivamente todas as propriedades, incluindo objetos e arrays aninhados. Uma maneira simples (com limitações, por exemplo, não lida bem com funções, datas, regex) é usar JSON.parse(JSON.stringify(obj)). Uma abordagem recursiva é mais robusta.

function deepClone(obj) { // Versão simples (com limitações) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Clonagem falhou:", e); return null; } // Mais robusto recursivo (exemplo básico): /* if (obj === null || typeof obj !== 'object') { return obj; } let clone = Array.isArray(obj) ? [] : {}; for (let key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { clone[key] = deepClone(obj[key]); } } return clone; */ } const original = { a: 1, b: { c: 2 } }; const cloned = deepClone(original); cloned.b.c = 3; console.log(original.b.c); // 2 (prova que é um clone profundo) console.log(cloned.b.c); // 3

Obter Chaves de um Objeto

Como obter um array de chaves de um objeto?

Explicação: Use Object.keys(obj).

function getKeys(obj) { return Object.keys(obj); } console.log(getKeys({a: 1, b: 2})); // ['a', 'b']

Obter Valores de um Objeto

Como obter um array de valores de um objeto?

Explicação: Use Object.values(obj).

function getValues(obj) { return Object.values(obj); } console.log(getValues({a: 1, b: 2})); // [1, 2]

Verificar se o Array Inclui Valor

Como verificar se um array contém um valor específico?

Explicação: Use Array.prototype.includes(value).

function checkIncludes(arr, val) { return arr.includes(val); } console.log(checkIncludes([1, 2, 3], 2)); // true

Mesclar Dois Arrays

Como mesclar dois arrays em um só?

Explicação: Use a sintaxe spread (...) ou Array.prototype.concat().

function mergeArrays(arr1, arr2) { return [...arr1, ...arr2]; } console.log(mergeArrays([1, 2], [3, 4])); // [1, 2, 3, 4]

Explicar 'this' no Escopo Global

A que se refere this no escopo global em um navegador?

Explicação: No contexto de execução global (fora de qualquer função), this se refere ao objeto global, que é window em navegadores web.

console.log(this === window); // true (em um ambiente de navegador)

Explicar 'this' em um Método de Objeto

A que se refere this quando usado dentro de um método de objeto?

Explicação: Quando uma função é chamada como um método de um objeto, this se refere ao objeto no qual o método é chamado.

const myObject = { prop: 'Hello', greet() { return this.prop; } }; console.log(myObject.greet()); // 'Hello'

Explicar 'this' com Arrow Functions

Como as arrow functions lidam com this?

Explicação: As arrow functions não possuem seu próprio contexto this. Em vez disso, elas herdam this do escopo (léxico) circundante onde são definidas.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Registra 42 porque 'this' é herdado }, 100); } new MyClass();

Diferença entre `let`, `const` e `var`

Quais são as principais diferenças entre let, const e var?

Explicação: var tem escopo de função e é içado (hoisted). let e const têm escopo de bloco e são içados, mas estão em uma 'zona morta temporal' até a declaração. const deve ser inicializado e não pode ser reatribuído.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Redefine e afeta o 'a' externo let b = 20; // Novo 'b' dentro do bloco // const c = 30; // Seria um novo 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

O que é uma Promise?

Explique o que é uma Promise em JavaScript.

Explicação: Uma Promise é um objeto que representa a eventual conclusão (ou falha) de uma operação assíncrona e seu valor resultante. Ela pode estar em um de três estados: pendente, cumprida ou rejeitada.

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Sucesso!'); // reject('Erro!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

Usando `async/await`

Como async e await simplificam o trabalho com Promises?

Explicação: async/await fornece açúcar sintático sobre Promises, fazendo com que o código assíncrono pareça e se comporte um pouco mais como código síncrono. Uma função async sempre retorna uma Promise, e await pausa a execução até que uma Promise se resolva.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Iniciando...'); await delay(1000); console.log('Esperou 1 segundo.'); await delay(500); console.log('Esperou mais 0.5 segundos.'); return 'Concluído!'; } run().then(console.log);

Converter String para Número

Como converter uma string para um número?

Explicação: Use parseInt(), parseFloat() ou o operador de adição unário (+).

const str = '123.45'; console.log(parseInt(str)); // 123 console.log(parseFloat(str)); // 123.45 console.log(+str); // 123.45

Converter Número para String

Como converter um número para uma string?

Explicação: Use String(), number.toString() ou concatenação de strings.

const num = 123; console.log(String(num)); // '123' console.log(num.toString()); // '123' console.log('' + num); // '123'

O que é `JSON.stringify`?

O que JSON.stringify faz?

Explicação: Ele converte um objeto ou valor JavaScript para uma string JSON.

const obj = { name: 'Alice', age: 30 }; const jsonString = JSON.stringify(obj); console.log(jsonString); // '{"name":"Alice","age":30}'

O que é `JSON.parse`?

O que JSON.parse faz?

Explicação: Ele analisa uma string JSON, construindo o valor ou objeto JavaScript descrito pela string.

const jsonString = '{"name":"Alice","age":30}'; const obj = JSON.parse(jsonString); console.log(obj.name); // 'Alice'

Operador Spread em Arrays

Como o operador spread é usado com arrays?

Explicação: Ele permite que um iterável (como um array) seja expandido em locais onde zero ou mais argumentos ou elementos são esperados. Útil para copiar, mesclar e adicionar elementos.

const arr1 = [1, 2]; const arr2 = [3, 4]; const combined = [...arr1, ...arr2]; // [1, 2, 3, 4] const copy = [...arr1]; // [1, 2]

Operador Spread em Objetos

Como o operador spread é usado com objetos?

Explicação: Ele permite copiar e mesclar propriedades de objetos.

const obj1 = { a: 1, b: 2 }; const obj2 = { b: 3, c: 4 }; const merged = { ...obj1, ...obj2 }; // { a: 1, b: 3, c: 4 } const copy = { ...obj1 }; // { a: 1, b: 2 }

Desestruturação de Arrays

Explique a desestruturação de array com um exemplo.

Explicação: É uma sintaxe que torna possível desempacotar valores de arrays em variáveis distintas.

const [a, b] = [10, 20]; console.log(a); // 10 console.log(b); // 20 const [x, , z] = [1, 2, 3]; console.log(z); // 3

Desestruturação de Objetos

Explique a desestruturação de objetos com um exemplo.

Explicação: Torna possível desempacotar propriedades de objetos em variáveis distintas.

const person = { name: 'Bob', age: 25 }; const { name, age } = person; console.log(name); // 'Bob' console.log(age); // 25 const { name: personName } = person; console.log(personName); // 'Bob'

Implementar `call`

Como você poderia implementar uma versão básica de Function.prototype.call?

Explicação: call invoca uma função com um valor this especificado e argumentos fornecidos individualmente. Você pode anexar a função ao contexto this, chamá-la e depois removê-la.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Usar uma chave única context[uniqueId] = this; const result = context[uniqueId](...args); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myCall({ name: 'Charlie' }, 'Hi', '!'); // Hi, Charlie!

Implementar `apply`

Como você poderia implementar uma versão básica de Function.prototype.apply?

Explicação: apply é semelhante a call, mas aceita argumentos como um array.

Function.prototype.myApply = function(context, argsArray) { context = context || window; const uniqueId = Symbol(); context[uniqueId] = this; const result = context[uniqueId](...(argsArray || [])); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myApply({ name: 'David' }, ['Hello', '.']); // Hello, David.

Explicar Event Loop

Explique brevemente o Event Loop do JavaScript.

Explicação: JavaScript é single-threaded, mas alcança concorrência usando um event loop. A 'call stack' lida com código síncrono. As Web APIs lidam com operações assíncronas (como setTimeout, fetch). Quando uma operação assíncrona termina, seu callback vai para a 'callback queue' (ou 'microtask queue' para Promises). O event loop verifica constantemente se a 'call stack' está vazia; se estiver, ele move o próximo callback da fila para a pilha para execução.

console.log('Início'); setTimeout(() => { console.log('Callback de Timeout'); // Vai para a Callback Queue }, 0); Promise.resolve().then(() => { console.log('Promise Resolvida'); // Vai para a Microtask Queue }); console.log('Fim'); // Ordem de Saída: Início, Fim, Promise Resolvida, Callback de Timeout // (Microtasks são executadas antes de Macrotasks/Callbacks)

Busca Binária

Implemente uma função de busca binária para um array ordenado.

Explicação: A busca binária encontra eficientemente um item em um array ordenado, dividindo repetidamente o intervalo de busca pela metade. Se o valor da chave de busca for menor que o item no meio do intervalo, reduza o intervalo para a metade inferior. Caso contrário, reduza para a metade superior. Você faz isso até que o valor seja encontrado ou o intervalo esteja vazio.

function binarySearch(sortedArray, key) { let start = 0; let end = sortedArray.length - 1; while (start <= end) { let middle = Math.floor((start + end) / 2); if (sortedArray[middle] === key) { return middle; // Encontrado } else if (sortedArray[middle] < key) { start = middle + 1; // Busca na metade direita } else { end = middle - 1; // Busca na metade esquerda } } return -1; // Não encontrado } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort

Implemente o algoritmo Merge Sort.

Explicação: Merge Sort é um algoritmo de divisão e conquista. Ele divide o array de entrada em duas metades, chama a si mesmo para as duas metades e, em seguida, mescla as duas metades ordenadas. A função de mesclagem é crucial; ela combina dois sub-arrays ordenados em um único array ordenado.

function mergeSort(arr) { if (arr.length <= 1) return arr; const mid = Math.floor(arr.length / 2); const left = mergeSort(arr.slice(0, mid)); const right = mergeSort(arr.slice(mid)); return merge(left, right); } function merge(left, right) { let result = []; let leftIndex = 0; let rightIndex = 0; while (leftIndex < left.length && rightIndex < right.length) { if (left[leftIndex] < right[rightIndex]) { result.push(left[leftIndex]); leftIndex++; } else { result.push(right[rightIndex]); rightIndex++; } } return result.concat(left.slice(leftIndex)).concat(right.slice(rightIndex)); } console.log(mergeSort([38, 27, 43, 3, 9, 82, 10])); // [3, 9, 10, 27, 38, 43, 82]

Quick Sort

Implemente o algoritmo Quick Sort.

Explicação: Quick Sort também é um algoritmo de divisão e conquista. Ele escolhe um elemento como pivô e particiona o array dado em torno do pivô escolhido. Elementos menores que o pivô vão para a esquerda, e elementos maiores vão para a direita. Ele então ordena recursivamente os sub-arrays.

function quickSort(arr) { if (arr.length <= 1) return arr; const pivot = arr[arr.length - 1]; const left = []; const right = []; for (let i = 0; i < arr.length - 1; i++) { if (arr[i] < pivot) { left.push(arr[i]); } else { right.push(arr[i]); } } return [...quickSort(left), pivot, ...quickSort(right)]; } console.log(quickSort([10, 8, 2, 1, 6, 3, 9, 4, 7, 5])); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

Bubble Sort

Implemente o algoritmo Bubble Sort.

Explicação: Bubble Sort é um algoritmo de ordenação simples que percorre repetidamente a lista, compara elementos adjacentes e os troca se estiverem na ordem errada. A passagem pela lista é repetida até que a lista esteja ordenada.

function bubbleSort(arr) { let n = arr.length; let swapped; do { swapped = false; for (let i = 0; i < n - 1; i++) { if (arr[i] > arr[i + 1]) { [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Troca swapped = true; } } n--; // Otimização: o último elemento já está no lugar } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Substrings Mais Longas Sem Caracteres Repetidos

Dada uma string, encontre o comprimento da substring mais longa sem caracteres repetidos.

Explicação: Use a técnica de 'janela deslizante'. Mantenha uma janela (substring) e um conjunto de caracteres nessa janela. Expanda a janela movendo o ponteiro direito. Se um caractere repetido for encontrado, diminua a janela da esquerda até que o caractere repetido seja removido.

function lengthOfLongestSubstring(s) { let maxLength = 0; let start = 0; const charMap = {}; for (let end = 0; end < s.length; end++) { const char = s[end]; if (charMap[char] >= start) { start = charMap[char] + 1; } charMap[char] = end; maxLength = Math.max(maxLength, end - start + 1); } return maxLength; } console.log(lengthOfLongestSubstring('abcabcbb')); // 3 ('abc') console.log(lengthOfLongestSubstring('bbbbb')); // 1 ('b') console.log(lengthOfLongestSubstring('pwwkew')); // 3 ('wke')

Implementar uma Lista Encadeada

Implemente uma Lista Encadeada Simples (Singly Linked List) com os métodos add e print.

Explicação: Uma lista encadeada é uma estrutura de dados linear onde os elementos não são armazenados em locais de memória contíguos. Cada elemento (nó) aponta para o próximo. Você precisa de uma classe Node e uma classe LinkedList para gerenciar a cabeça e adicionar novos nós.

class Node { constructor(data, next = null) { this.data = data; this.next = next; } } class LinkedList { constructor() { this.head = null; } add(data) { const newNode = new Node(data); if (!this.head) { this.head = newNode; } else { let current = this.head; while (current.next) { current = current.next; } current.next = newNode; } } print() { let current = this.head; let list = ''; while (current) { list += current.data + ' -> '; current = current.next; } console.log(list + 'null'); } } const list = new LinkedList(); list.add(10); list.add(20); list.add(30); list.print(); // 10 -> 20 -> 30 -> null

Implementar uma Árvore de Busca Binária (BST)

Implemente uma Árvore de Busca Binária (BST) com os métodos insert e find.

Explicação: Uma BST é uma estrutura de dados de árvore binária baseada em nós que possui as seguintes propriedades: A subárvore esquerda de um nó contém apenas nós com chaves menores que a chave do nó. A subárvore direita de um nó contém apenas nós com chaves maiores que a chave do nó. Ambas as subárvores também devem ser árvores de busca binária.

class Node { constructor(data) { this.data = data; this.left = null; this.right = null; } } class BST { constructor() { this.root = null; } insert(data) { const newNode = new Node(data); if (!this.root) { this.root = newNode; return; } this._insertNode(this.root, newNode); } _insertNode(node, newNode) { if (newNode.data < node.data) { if (!node.left) node.left = newNode; else this._insertNode(node.left, newNode); } else { if (!node.right) node.right = newNode; else this._insertNode(node.right, newNode); } } find(data) { return this._findNode(this.root, data); } _findNode(node, data) { if (!node) return null; if (data < node.data) return this._findNode(node.left, data); else if (data > node.data) return this._findNode(node.right, data); else return node; } } const bst = new BST(); bst.insert(10); bst.insert(5); bst.insert(15); bst.insert(7); console.log(bst.find(7)); // Node { data: 7, left: null, right: null } console.log(bst.find(12)); // null

Rotacionar um Array

Escreva uma função para rotacionar um array para a direita em k passos.

Explicação: Rotacionar um array significa mover seus elementos. Para uma rotação para a direita, o último elemento se torna o primeiro. Repetir isso k vezes funciona, mas é ineficiente. Uma maneira melhor é usar manipulação de array ou calcular as novas posições.

function rotateArray(nums, k) { k = k % nums.length; // Lida com casos em que k > length if (k === 0) return nums; const partToMove = nums.splice(nums.length - k); nums.unshift(...partToMove); return nums; } console.log(rotateArray([1, 2, 3, 4, 5, 6, 7], 3)); // [5, 6, 7, 1, 2, 3, 4]

Encontrar Interseção de Dois Arrays

Dados dois arrays, escreva uma função para calcular sua interseção (elementos comuns a ambos).

Explicação: Uma boa abordagem é converter um array em um Set para buscas em tempo médio de O(1). Em seguida, itere pelo segundo array e verifique se cada elemento existe no conjunto. Colete as correspondências.

function intersection(nums1, nums2) { const set1 = new Set(nums1); const resultSet = new Set(); for (const num of nums2) { if (set1.has(num)) { resultSet.add(num); } } return [...resultSet]; } console.log(intersection([1, 2, 2, 1], [2, 2])); // [2] console.log(intersection([4, 9, 5], [9, 4, 9, 8, 4])); // [9, 4]

Agrupar Anagramas

Dado um array de strings, agrupe os anagramas juntos.

Explicação: A chave é encontrar uma assinatura única para cada grupo de anagramas. Uma maneira comum é ordenar cada palavra alfabeticamente. Palavras que resultam na mesma string ordenada são anagramas. Use um mapa hash onde as chaves são palavras ordenadas e os valores são arrays de palavras originais.

function groupAnagrams(strs) { const map = {}; for (const str of strs) { const sortedStr = str.split('').sort().join(''); if (!map[sortedStr]) { map[sortedStr] = []; } map[sortedStr].push(str); } return Object.values(map); } console.log(groupAnagrams(['eat', 'tea', 'tan', 'ate', 'nat', 'bat'])); // Saída: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

Mover Zeros para o Final

Dado um array nums, escreva uma função para mover todos os 0s para o final dele, mantendo a ordem relativa dos elementos não-zero.

Explicação: Use uma abordagem de 'dois ponteiros' ou 'bola de neve'. Um ponteiro acompanha a posição onde o próximo elemento não-zero deve ir. Itere pelo array; se um elemento não for zero, coloque-o na posição do ponteiro e incremente o ponteiro. Finalmente, preencha o restante com zeros.

function moveZeroes(nums) { let nonZeroIndex = 0; // Mover todos os elementos não-zero para a frente for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Preencher o restante com zeros for (let i = nonZeroIndex; i < nums.length; i++) { nums[i] = 0; } return nums; } console.log(moveZeroes([0, 1, 0, 3, 12])); // [1, 3, 12, 0, 0]

Sub-array Máximo (Algoritmo de Kadane)

Dado um array de inteiros nums, encontre o sub-array contíguo (contendo pelo menos um número) que tem a maior soma e retorne sua soma.

Explicação: O algoritmo de Kadane é uma maneira eficiente de resolver isso. Itere pelo array, acompanhando a soma currentMax terminando na posição atual e a soma globalMax encontrada até agora. Se currentMax se tornar negativo, redefina-o para 0 (ou melhor, para o elemento atual).

function maxSubArray(nums) { let globalMax = -Infinity; let currentMax = 0; for (let i = 0; i < nums.length; i++) { currentMax = Math.max(nums[i], currentMax + nums[i]); if (currentMax > globalMax) { globalMax = currentMax; } } return globalMax; } console.log(maxSubArray([-2, 1, -3, 4, -1, 2, 1, -5, 4])); // 6 (de [4, -1, 2, 1])

Permutações de uma String

Escreva uma função para gerar todas as permutações de uma determinada string.

Explicação: Isso é tipicamente resolvido usando recursão e 'backtracking'. Para cada caractere, fixe-o e gere recursivamente permutações para o resto da string. Caso base: quando a string tem apenas um caractere, retorne-o.

function stringPermutations(str) { if (str.length === 0) return ['']; if (str.length === 1) return [str]; const results = []; for (let i = 0; i < str.length; i++) { const char = str[i]; const remainingChars = str.slice(0, i) + str.slice(i + 1); const perms = stringPermutations(remainingChars); for (const perm of perms) { results.push(char + perm); } } return [...new Set(results)]; // Usar Set para lidar com caracteres duplicados, se necessário } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Máximo Divisor Comum (MDC)

Escreva uma função para encontrar o Máximo Divisor Comum (MDC) de dois números.

Explicação: O algoritmo euclidiano é um método eficiente. Se b for 0, a é o MDC. Caso contrário, o MDC de a e b é o mesmo que o MDC de b e a % b (o resto de a dividido por b).

function gcd(a, b) { while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } console.log(gcd(48, 18)); // 6 console.log(gcd(101, 103)); // 1

Mínimo Múltiplo Comum (MMC)

Escreva uma função para encontrar o Mínimo Múltiplo Comum (MMC) de dois números.

Explicação: O MMC pode ser calculado usando o MDC com a fórmula: MMC(a, b) = |a * b| / MDC(a, b).

function gcd(a, b) { // Helper do problema anterior while (b !== 0) { let temp = b; b = a % b; a = temp; } return a; } function lcm(a, b) { if (a === 0 || b === 0) return 0; return Math.abs(a * b) / gcd(a, b); } console.log(lcm(15, 20)); // 60 console.log(lcm(7, 5)); // 35

Implementar Promise.all

Implemente uma função que se comporta como Promise.all.

Explicação: Promise.all recebe um array de promises e retorna uma única promise que resolve quando todas as promises de entrada resolveram, ou rejeita se alguma promise de entrada rejeitar. O valor resolvido é um array dos valores resolvidos.

function myPromiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; const numPromises = promises.length; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; completedCount++; if (completedCount === numPromises) { resolve(results); } }) .catch(reject); // Rejeitar imediatamente em qualquer erro }); }); } // Exemplo de uso: const p1 = Promise.resolve(3); const p2 = 42; const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); myPromiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, 'foo']

Inverter Árvore Binária

Escreva uma função para inverter uma árvore binária.

Explicação: Para inverter uma árvore binária, você troca os filhos esquerdo e direito para cada nó. Isso pode ser feito recursivamente ou iterativamente (usando uma fila ou pilha).

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function invertTree(root) { if (root === null) { return null; } // Troca os filhos [root.left, root.right] = [root.right, root.left]; // Recur para os filhos esquerdo e direito invertTree(root.left); invertTree(root.right); return root; } // Exemplo: 4 -> [2, 7] -> [1, 3, 6, 9] // Torna-se: 4 -> [7, 2] -> [9, 6, 3, 1]

Profundidade Máxima de Árvore Binária

Dada uma árvore binária, encontre sua profundidade máxima.

Explicação: A profundidade máxima é o número de nós ao longo do caminho mais longo do nó raiz até o nó folha mais distante. Isso pode ser encontrado recursivamente: ProfundidadeMáxima = 1 + max(ProfundidadeMáxima(esquerda), ProfundidadeMáxima(direita)). O caso base é quando um nó é nulo, sua profundidade é 0.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function maxDepth(root) { if (root === null) { return 0; } const leftDepth = maxDepth(root.left); const rightDepth = maxDepth(root.right); return Math.max(leftDepth, rightDepth) + 1; } // Exemplo: 3 -> [9, 20] -> [null, null, 15, 7] const tree = new Node(3, new Node(9), new Node(20, new Node(15), new Node(7))); console.log(maxDepth(tree)); // 3

Melhor Momento para Comprar/Vender Ações

Você recebe um array prices onde prices[i] é o preço de uma determinada ação no i-ésimo dia. Você quer maximizar seu lucro escolhendo um único dia para comprar uma ação e escolhendo um dia diferente no futuro para vender essa ação. Retorne o lucro máximo.

Explicação: Itere pelos preços, acompanhando o preço mínimo encontrado até agora (minPrice) e o lucro máximo encontrado até agora (maxProfit). Para cada dia, calcule o lucro potencial se você vendesse hoje (preço atual - minPrice) e atualize maxProfit se for maior.

function maxProfit(prices) { let minPrice = Infinity; let maxProfit = 0; for (let i = 0; i < prices.length; i++) { if (prices[i] < minPrice) { minPrice = prices[i]; } else if (prices[i] - minPrice > maxProfit) { maxProfit = prices[i] - minPrice; } } return maxProfit; } console.log(maxProfit([7, 1, 5, 3, 6, 4])); // 5 (Comprar em 1, Vender em 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (Nenhum lucro possível)

Número Único

Dado um array não vazio de inteiros nums, cada elemento aparece duas vezes, exceto um. Encontre esse único.

Explicação: Uma maneira muito eficiente de resolver isso é usando o operador bitwise XOR (^). Fazer um XOR de um número com ele mesmo resulta em 0. Fazer um XOR de um número com 0 resulta no próprio número. Se você fizer um XOR de todos os números no array, os pares se cancelarão (tornando-se 0), deixando apenas o número único.

function singleNumber(nums) { let result = 0; for (const num of nums) { result ^= num; } return result; } console.log(singleNumber([2, 2, 1])); // 1 console.log(singleNumber([4, 1, 2, 1, 2])); // 4

Elemento Majoritário

Dado um array nums de tamanho n, retorne o elemento majoritário. O elemento majoritário é o elemento que aparece mais de ⌊n / 2⌋ vezes.

Explicação: O algoritmo de votação de Boyer-Moore é uma maneira eficiente. Inicialize um candidate e um count. Itere pelo array. Se count for 0, defina o elemento atual como o candidate. Se o elemento atual corresponder ao candidate, incremente count; caso contrário, decremente count.

function majorityElement(nums) { let candidate = null; let count = 0; for (const num of nums) { if (count === 0) { candidate = num; } count += (num === candidate) ? 1 : -1; } return candidate; } console.log(majorityElement([3, 2, 3])); // 3 console.log(majorityElement([2, 2, 1, 1, 1, 2, 2])); // 2

Escadas de Subida

Você está subindo uma escada. Leva n degraus para chegar ao topo. A cada vez você pode subir 1 ou 2 degraus. De quantas maneiras distintas você pode subir ao topo?

Explicação: Este é um problema clássico de programação dinâmica, muito semelhante à sequência de Fibonacci. O número de maneiras de alcançar o degrau n é a soma das maneiras de alcançar o degrau n-1 (dando 1 degrau) e das maneiras de alcançar o degrau n-2 (dando 2 degraus). dp[n] = dp[n-1] + dp[n-2].

function climbStairs(n) { if (n <= 2) return n; let oneStepBefore = 2; let twoStepsBefore = 1; let allWays = 0; for (let i = 3; i <= n; i++) { allWays = oneStepBefore + twoStepsBefore; twoStepsBefore = oneStepBefore; oneStepBefore = allWays; } return allWays; } console.log(climbStairs(2)); // 2 (1+1, 2) console.log(climbStairs(3)); // 3 (1+1+1, 1+2, 2+1) console.log(climbStairs(4)); // 5

Produto do Array Exceto Ele Próprio

Dado um array de inteiros nums, retorne um array answer tal que answer[i] seja igual ao produto de todos os elementos de nums exceto nums[i]. Não use o operador de divisão.

Explicação: Você pode resolver isso calculando produtos de prefixo e produtos de sufixo. O resultado no índice i é o produto de todos os elementos antes de i multiplicado pelo produto de todos os elementos depois de i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calcular produtos de prefixo let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calcular produtos de sufixo e multiplicar com o prefixo let suffix = 1; for (let i = n - 1; i >= 0; i--) { answer[i] *= suffix; suffix *= nums[i]; } return answer; } console.log(productExceptSelf([1, 2, 3, 4])); // [24, 12, 8, 6]

Número de Ilhas

Dada uma grade 2D de '1's (terra) e '0's (água), conte o número de ilhas. Uma ilha é cercada por água e é formada conectando terras adjacentes horizontalmente ou verticalmente.

Explicação: Itere por cada célula da grade. Se você encontrar um '1' (terra), incremente a contagem de ilhas e inicie uma Busca em Profundidade (DFS) ou Busca em Largura (BFS) a partir dessa célula. Durante a busca, marque todos os '1's conectados como '0' (ou visitados) para evitar recontá-los.

function numIslands(grid) { if (!grid || grid.length === 0) return 0; let count = 0; const rows = grid.length; const cols = grid[0].length; function dfs(r, c) { if (r < 0 || c < 0 || r >= rows || c >= cols || grid[r][c] === '0') { return; } grid[r][c] = '0'; // Marcar como visitado dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1); } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === '1') { count++; dfs(r, c); } } } return count; } const grid1 = [ ['1', '1', '1', '1', '0'], ['1', '1', '0', '1', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '0', '0', '0'] ]; // console.log(numIslands(grid1)); // 1 (Precisa executar em uma cópia ou restaurar a grade)

Cache LRU

Projete e implemente uma estrutura de dados para Cache de Uso Menos Recente (LRU). Ela deve suportar as operações get e put.

Explicação: Um cache LRU remove o item menos recentemente usado quando a capacidade é atingida. Uma implementação comum usa um Map (ou um mapa hash) para buscas em O(1) em tempo médio e uma Lista Duplamente Encadeada para atualizações em O(1) do status de 'recentemente usado'. O Map do JavaScript mantém a ordem de inserção, o que pode simplificar uma implementação básica.

class LRUCache { constructor(capacity) { this.capacity = capacity; this.cache = new Map(); } get(key) { if (!this.cache.has(key)) return -1; const value = this.cache.get(key); this.cache.delete(key); // Remover para reinserir no 'final' (mais recente) this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.put(1, 1); cache.put(2, 2); console.log(cache.get(1)); // 1 cache.put(3, 3); // Remove 2 console.log(cache.get(2)); // -1

Gerar Parênteses

Dadas n pares de parênteses, escreva uma função para gerar todas as combinações de parênteses bem-formados.

Explicação: Este é um problema clássico de 'backtracking'. Mantenha contagens para parênteses abertos e fechados. Você pode adicionar um parêntese aberto se open < n. Você pode adicionar um parêntese fechado se close < open. O caso base é quando o comprimento da string atinge 2 * n.

function generateParenthesis(n) { const results = []; function backtrack(currentString, openCount, closeCount) { if (currentString.length === n * 2) { results.push(currentString); return; } if (openCount < n) { backtrack(currentString + '(', openCount + 1, closeCount); } if (closeCount < openCount) { backtrack(currentString + ')', openCount, closeCount + 1); } } backtrack('', 0, 0); return results; } console.log(generateParenthesis(3)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Contêiner com Mais Água

Dados n inteiros não negativos a1, a2, ..., an, onde cada um representa um ponto na coordenada (i, ai). n linhas verticais são desenhadas de forma que os dois pontos finais da linha i estejam em (i, ai) e (i, 0). Encontre duas linhas que, juntamente com o eixo x, formam um contêiner, de modo que o contêiner contenha a maior quantidade de água.

Explicação: Use a abordagem de dois ponteiros. Comece com um ponteiro no início e um no final. Calcule a área. A área é limitada pela linha mais curta. Para potencialmente encontrar uma área maior, mova o ponteiro que aponta para a linha mais curta para dentro.

function maxArea(height) { let max = 0; let left = 0; let right = height.length - 1; while (left < right) { const h = Math.min(height[left], height[right]); const w = right - left; max = Math.max(max, h * w); if (height[left] < height[right]) { left++; } else { right--; } } return max; } console.log(maxArea([1, 8, 6, 2, 5, 4, 8, 3, 7])); // 49

3Sum

Dado um array nums de n inteiros, existem elementos a, b, c em nums tais que a + b + c = 0? Encontre todos os trigêmeos únicos no array que resultam na soma de zero.

Explicação: Primeiro, ordene o array. Em seguida, itere pelo array com um ponteiro i. Para cada i, use mais dois ponteiros, left (começando em i+1) e right (começando no final), para encontrar dois números que somam -nums[i]. Lide com duplicatas para garantir trigêmeos únicos.

function threeSum(nums) { nums.sort((a, b) => a - b); const results = []; for (let i = 0; i < nums.length - 2; i++) { if (i > 0 && nums[i] === nums[i - 1]) continue; // Pular duplicatas para i let left = i + 1; let right = nums.length - 1; let target = -nums[i]; while (left < right) { let sum = nums[left] + nums[right]; if (sum === target) { results.push([nums[i], nums[left], nums[right]]); while (left < right && nums[left] === nums[left + 1]) left++; // Pular duplicatas while (left < right && nums[right] === nums[right - 1]) right--; // Pular duplicatas left++; right--; } else if (sum < target) { left++; } else { right--; } } } return results; } console.log(threeSum([-1, 0, 1, 2, -1, -4])); // [[-1, -1, 2], [-1, 0, 1]]

Posição de Inserção de Busca

Dado um array ordenado de inteiros distintos e um valor alvo, retorne o índice se o alvo for encontrado. Se não, retorne o índice onde ele estaria se fosse inserido em ordem.

Explicação: Esta é uma variação da busca binária. Realize a busca binária, mas se o elemento não for encontrado, o ponteiro start terminará na posição onde o elemento deve ser inserido.

function searchInsert(nums, target) { let start = 0; let end = nums.length - 1; while (start <= end) { let mid = Math.floor((start + end) / 2); if (nums[mid] === target) { return mid; } else if (nums[mid] < target) { start = mid + 1; } else { end = mid - 1; } } return start; // Se não encontrado, start é o ponto de inserção } console.log(searchInsert([1, 3, 5, 6], 5)); // 2 console.log(searchInsert([1, 3, 5, 6], 2)); // 1 console.log(searchInsert([1, 3, 5, 6], 7)); // 4

Mesclar Duas Listas Ordenadas (Listas Encadeadas)

Mescle duas listas encadeadas ordenadas e retorne-a como uma nova lista ordenada. A nova lista deve ser feita unindo os nós das duas primeiras listas.

Explicação: Use um nó dummy (cabeça fantasma) para simplificar o processo. Use um ponteiro para construir a nova lista. Compare os nós atuais de ambas as listas de entrada e anexe o menor à nova lista, avançando o ponteiro dessa lista. Repita até que uma lista se esgote e, em seguida, anexe o restante da outra lista.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function mergeTwoLists(l1, l2) { let dummyHead = new ListNode(); let current = dummyHead; while (l1 !== null && l2 !== null) { if (l1.val < l2.val) { current.next = l1; l1 = l1.next; } else { current.next = l2; l2 = l2.next; } current = current.next; } current.next = l1 !== null ? l1 : l2; return dummyHead.next; } // Exemplo: 1->2->4 e 1->3->4 => 1->1->2->3->4->4

Árvore Simétrica

Dada uma árvore binária, verifique se ela é um espelho de si mesma (ou seja, simétrica em torno do seu centro).

Explicação: Isso pode ser resolvido recursivamente. Uma árvore é simétrica se a subárvore esquerda é uma imagem espelhada da subárvore direita. Crie uma função auxiliar isMirror(t1, t2) que verifica se duas árvores são espelhos. Ela deve verificar: 1. t1.val === t2.val. 2. t1.left é um espelho de t2.right. 3. t1.right é um espelho de t2.left.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function isSymmetric(root) { if (!root) return true; function isMirror(t1, t2) { if (!t1 && !t2) return true; if (!t1 || !t2 || t1.val !== t2.val) return false; return isMirror(t1.left, t2.right) && isMirror(t1.right, t2.left); } return isMirror(root.left, root.right); } // Exemplo: 1 -> [2, 2] -> [3, 4, 4, 3] é simétrico.

Travessia em Nível (BST/Árvore)

Dada uma árvore binária, retorne a travessia em nível dos valores dos seus nós. (ou seja, da esquerda para a direita, nível por nível).

Explicação: Isso é uma Busca em Largura (BFS). Use uma fila. Comece adicionando a raiz à fila. Enquanto a fila não estiver vazia, processe todos os nós no nível atual. Para cada nó processado, adicione seus filhos (se existirem) à fila para o próximo nível.

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function levelOrder(root) { if (!root) return []; const result = []; const queue = [root]; while (queue.length > 0) { const levelSize = queue.length; const currentLevel = []; for (let i = 0; i < levelSize; i++) { const node = queue.shift(); currentLevel.push(node.val); if (node.left) queue.push(node.left); if (node.right) queue.push(node.right); } result.push(currentLevel); } return result; } // Exemplo: 3 -> [9, 20] -> [null, null, 15, 7] // Saída: [[3], [9, 20], [15, 7]]

Converter Array Ordenado para BST Balanceada em Altura

Dado um array onde os elementos estão ordenados em ordem crescente, converta-o para uma árvore de busca binária (BST) balanceada em altura.

Explicação: Para criar uma BST balanceada em altura, você deve escolher o elemento do meio do array como a raiz. Em seguida, construa recursivamente a subárvore esquerda a partir da metade esquerda do array e a subárvore direita a partir da metade direita.

class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function sortedArrayToBST(nums) { if (!nums || nums.length === 0) return null; function buildBST(start, end) { if (start > end) return null; const mid = Math.floor((start + end) / 2); const node = new TreeNode(nums[mid]); node.left = buildBST(start, mid - 1); node.right = buildBST(mid + 1, end); return node; } return buildBST(0, nums.length - 1); } // Exemplo: [-10, -3, 0, 5, 9] // Saída: Uma árvore como [0, -3, 9, -10, null, 5, null]

Implementar `bind`

Implemente sua própria versão de Function.prototype.bind.

Explicação: bind cria uma nova função que, quando chamada, tem sua palavra-chave this definida para o valor fornecido, com uma dada sequência de argumentos precedendo quaisquer argumentos fornecidos quando a nova função é chamada. Use apply ou call dentro de uma função retornada, lidando com aplicação parcial (argumentos pré-definidos).

Function.prototype.myBind = function(context, ...bindArgs) { const originalFunc = this; return function(...callArgs) { return originalFunc.apply(context, [...bindArgs, ...callArgs]); }; } const module = { x: 42, getX: function() { return this.x; } }; const unboundGetX = module.getX; console.log(unboundGetX()); // undefined (this é global/window) const boundGetX = unboundGetX.myBind(module); console.log(boundGetX()); // 42

Encontrar o K-ésimo Maior Elemento

Encontre o k-ésimo maior elemento em um array não ordenado. Observe que é o k-ésimo maior elemento na ordem ordenada, não o k-ésimo elemento distinto.

Explicação: Uma abordagem simples é ordenar o array e, em seguida, pegar o elemento no índice n - k. Uma abordagem mais eficiente para arrays grandes envolve o uso de um min-heap ou um algoritmo de seleção como o Quickselect.

function findKthLargest(nums, k) { // Abordagem simples: Ordenar nums.sort((a, b) => b - a); // Ordenar em ordem decrescente return nums[k - 1]; // Nota: Quickselect seria mais eficiente em uma entrevista // mas a ordenação é mais fácil de implementar rapidamente. } console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5 console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4

Verificar se Objeto possui Propriedade

Como você pode verificar se um objeto tem uma propriedade específica?

Explicação: Você pode usar o operador in (verifica propriedades próprias e herdadas), Object.prototype.hasOwnProperty.call(obj, prop) (verifica apenas propriedades próprias), ou simplesmente obj.prop !== undefined (pode ser complicado com valores undefined).

const person = { name: 'Eve', age: 28 }; function hasProp(obj, prop) { console.log(`Usando 'in': ${prop in obj}`); console.log(`Usando 'hasOwnProperty': ${Object.prototype.hasOwnProperty.call(obj, prop)}`); } hasProp(person, 'name'); // true, true hasProp(person, 'toString'); // true, false (toString é herdado)

Inteiro para Romano

Escreva uma função para converter um inteiro para sua representação em numeral romano.

Explicação: Crie um mapeamento de numerais romanos e seus valores correspondentes, ordenados do maior para o menor. Itere sobre este mapa. Para cada valor, subtraia-o do número de entrada o maior número de vezes possível, anexando o numeral romano a cada vez.

function intToRoman(num) { const map = { M: 1000, CM: 900, D: 500, CD: 400, C: 100, XC: 90, L: 50, XL: 40, X: 10, IX: 9, V: 5, IV: 4, I: 1 }; let result = ''; for (let key in map) { while (num >= map[key]) { result += key; num -= map[key]; } } return result; } console.log(intToRoman(3)); // III console.log(intToRoman(58)); // LVIII console.log(intToRoman(1994)); // MCMXCIV

Romano para Inteiro

Escreva uma função para converter um numeral romano para um inteiro.

Explicação: Crie um mapa de símbolos romanos para valores. Itere pela string. Se o valor do símbolo atual for menor que o valor do próximo símbolo (como 'IV' ou 'IX'), subtraia o valor atual; caso contrário, adicione-o.

function romanToInt(s) { const map = { I: 1, V: 5, X: 10, L: 50, C: 100, D: 500, M: 1000 }; let result = 0; for (let i = 0; i < s.length; i++) { const currentVal = map[s[i]]; const nextVal = map[s[i + 1]]; if (nextVal > currentVal) { result -= currentVal; } else { result += currentVal; } } return result; } console.log(romanToInt('III')); // 3 console.log(romanToInt('LVIII')); // 58 console.log(romanToInt('MCMXCIV')); // 1994

Implementar um `Set`

Implemente uma estrutura de dados Set básica (sem usar o Set embutido) com os métodos add, has, delete e size.

Explicação: Você pode usar um objeto JavaScript (mapa de hash) como armazenamento subjacente. As chaves serão os elementos do conjunto (você pode precisar lidar com a forma de armazenar diferentes tipos e garantir a unicidade como chaves).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // Armazena o elemento this._size++; return true; } return false; } delete(element) { if (this.has(element)) { delete this.items[element]; this._size--; return true; } return false; } has(element) { return Object.prototype.hasOwnProperty.call(this.items, element); } get size() { return this._size; } } const set = new MySet(); set.add(1); set.add(2); console.log(set.has(1)); // true console.log(set.size); // 2 set.delete(1); console.log(set.has(1)); // false

Triângulo de Pascal

Dado um número inteiro numRows, gere as primeiras numRows do triângulo de Pascal.

Explicação: No triângulo de Pascal, cada número é a soma dos dois números diretamente acima dele. Comece com [[1]]. Para cada linha subsequente, comece e termine com 1. Cada elemento do meio é a soma do elemento no mesmo índice e o índice anterior da linha acima.

function generatePascalsTriangle(numRows) { if (numRows === 0) return []; const triangle = [[1]]; for (let i = 1; i < numRows; i++) { const prevRow = triangle[i - 1]; const newRow = [1]; for (let j = 1; j < i; j++) { newRow.push(prevRow[j - 1] + prevRow[j]); } newRow.push(1); triangle.push(newRow); } return triangle; } console.log(generatePascalsTriangle(5)); // [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1], [1, 4, 6, 4, 1]]

Busca de Palavra

Dado um tabuleiro 2D e uma palavra, encontre se a palavra existe na grade. A palavra pode ser construída a partir de letras de células sequencialmente adjacentes, onde células 'adjacentes' são vizinhas horizontalmente ou verticalmente.

Explicação: Isso requer uma Busca em Profundidade (DFS) com retrocesso. Itere por cada célula. Se uma célula corresponder à primeira letra da palavra, inicie uma DFS. A função DFS verifica vizinhos, garantindo que eles correspondam à próxima letra e não tenham sido visitados no caminho atual. Se um caminho falhar, retroceda desmarcando a célula.

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // Palavra encontrada if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Marcar como visitado const found = dfs(r + 1, c, index + 1) || dfs(r - 1, c, index + 1) || dfs(r, c + 1, index + 1) || dfs(r, c - 1, index + 1); board[r][c] = temp; // Retroceder return found; } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (board[r][c] === word[0] && dfs(r, c, 0)) { return true; } } } return false; } const board = [['A','B','C','E'],['S','F','C','S'],['A','D','E','E']]; console.log(exist(board, 'ABCCED')); // true console.log(exist(board, 'SEE')); // true console.log(exist(board, 'ABCB')); // false

Substring de Janela Mínima

Dadas duas strings s e t, encontre a janela mínima em s que conterá todos os caracteres em t. Se não houver tal janela em s que cubra todos os caracteres em t, retorne a string vazia "".

Explicação: Use uma abordagem de janela deslizante com dois ponteiros (left e right) e mapas de hash. Um mapa armazena as contagens de caracteres necessárias de t. Outro mapa armazena as contagens na janela atual. Expanda a janela com right. Uma vez que a janela contém todos os caracteres necessários, tente encolhê-la com left para encontrar a janela mínima possível.

function minWindow(s, t) { if (!t || !s || s.length < t.length) return ""; const tMap = {}; for (const char of t) tMap[char] = (tMap[char] || 0) + 1; let required = Object.keys(tMap).length; let formed = 0; const windowMap = {}; let left = 0; let minLen = Infinity; let result = ""; for (let right = 0; right < s.length; right++) { const char = s[right]; windowMap[char] = (windowMap[char] || 0) + 1; if (tMap[char] && windowMap[char] === tMap[char]) { formed++; } while (left <= right && formed === required) { if (right - left + 1 < minLen) { minLen = right - left + 1; result = s.substring(left, right + 1); } const leftChar = s[left]; windowMap[leftChar]--; if (tMap[leftChar] && windowMap[leftChar] < tMap[leftChar]) { formed--; } left++; } } return result; } console.log(minWindow('ADOBECODEBANC', 'ABC')); // 'BANC'

Inverter Lista Encadeada

Dada a head de uma lista encadeada simplesmente, inverta a lista e retorne a lista invertida.

Explicação: Você precisa iterar pela lista, alterando o ponteiro next de cada nó para apontar para o nó anterior. Mantenha o controle dos nós previous, current e next durante a iteração.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function reverseList(head) { let prev = null; let current = head; let next = null; while (current !== null) { next = current.next; // Armazenar o próximo nó current.next = prev; // Inverter o ponteiro do nó atual prev = current; // Mover prev um passo à frente current = next; // Mover current um passo à frente } return prev; // A nova head é o último 'prev' } // Exemplo: 1->2->3->4->5 torna-se 5->4->3->2->1

Detectar Ciclo em Lista Encadeada

Dada a head, a cabeça de uma lista encadeada, determine se a lista encadeada tem um ciclo.

Explicação: Use o algoritmo 'Tartaruga e Lebre de Floyd'. Tenha dois ponteiros, um movendo um passo de cada vez (slow) e outro movendo dois passos de cada vez (fast). Se houver um ciclo, o ponteiro fast eventualmente alcançará o ponteiro slow.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function hasCycle(head) { if (!head || !head.next) return false; let slow = head; let fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; // Chegou ao fim, sem ciclo slow = slow.next; fast = fast.next.next; } return true; // Ponteiros se encontraram, ciclo existe } // Exemplo: 3->2->0->-4 com -4 apontando de volta para 2. hasCycle retorna true.

Implementar `Object.create`

Implemente uma função que imita o comportamento de Object.create(proto).

Explicação: Object.create cria um novo objeto, usando um objeto existente como protótipo do objeto recém-criado. Você pode conseguir isso criando uma função construtora temporária, definindo seu protótipo para o proto de entrada e, em seguida, retornando uma nova instância dele.

function myObjectCreate(proto) { if (typeof proto !== 'object' && typeof proto !== 'function') { if (proto !== null) { throw new TypeError('Object prototype may only be an Object or null: ' + proto); } } function F() {} F.prototype = proto; return new F(); } const person = { isHuman: false, printIntroduction: function() { console.log(`My name is ${this.name}. Am I human? ${this.isHuman}`); } }; const me = myObjectCreate(person); me.name = 'Matthew'; me.isHuman = true; me.printIntroduction(); // My name is Matthew. Am I human? true console.log(Object.getPrototypeOf(me) === person); // true

O que é Hoisting?

Explique hoisting em JavaScript e forneça um exemplo.

Explicação: Hoisting é o comportamento padrão do JavaScript de mover declarações para o topo do escopo atual (global ou função) antes da execução do código. Declarações de variáveis (var) são 'hoisted' e inicializadas com undefined. Declarações de funções são totalmente 'hoisted' (nome e corpo). let e const são 'hoisted' mas não inicializadas, levando a uma Zona Morta Temporal.

console.log(myVar); // undefined (var é 'hoisted' e inicializado com undefined) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (Declaração de função é totalmente 'hoisted') var myVar = 'I am a var'; let myLet = 'I am a let'; function myFunc() { console.log('Hello!'); }

Explique Event Bubbling e Capturing

Explique Event Bubbling e Capturing no contexto do DOM.

Explicação: Estas são duas fases de propagação de eventos no HTML DOM. Fase de Captura: O evento viaja do window para o elemento alvo. Fase de Borbulhamento: O evento viaja do elemento alvo de volta para o window. Por padrão, a maioria dos manipuladores de eventos é registrada durante a fase de borbulhamento. Você pode usar addEventListener(type, listener, useCapture) com useCapture = true para lidar com eventos durante a fase de captura.

// Em uma estrutura HTML: <div><p><span>Click Me</span></p></div> // Se você clicar no <span>: // Captura: window -> document -> html -> body -> div -> p -> span // Borbulhamento: span -> p -> div -> body -> html -> document -> window /* Exemplo JS div.addEventListener('click', () => console.log('Div clicked'), true); // Captura p.addEventListener('click', () => console.log('P clicked'), false); // Borbulhamento span.addEventListener('click', () => console.log('Span clicked'), false); // Borbulhamento // A saída seria: Div clicked, Span clicked, P clicked */

Implementar `JSON.parse` manualmente (básico)

Tente implementar uma versão muito básica de JSON.parse (lidar com objetos simples, arrays, strings, números).

Explicação: Esta é uma tarefa muito complexa na íntegra, mas para um ambiente de codificação ao vivo, você pode ser solicitado a lidar com um subconjunto muito simplificado. Você precisaria analisar a string, identificando limites de objeto {} e array [], pares chave-valor ("key": value) e tipos de dados básicos. eval ou new Function podem trapacear isso, mas são perigosos. Um analisador real usaria um lexer/tokenizador e um analisador.

function basicJsonParse(jsonString) { // AVISO: Usar new Function é inseguro como eval. // Este é um exemplo simplificado e INSEGURO apenas para demonstração. // Uma implementação real requer um analisador adequado. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('JSON inválido: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

Achatar um Array Aninhado Profundamente

Escreva uma função para achatar um array aninhado profundamente.

Explicação: Ao contrário do achatar anterior (um nível), este precisa lidar com qualquer nível de aninhamento. A recursão é um ajuste natural. Itere pelo array. Se um elemento for um array, chame recursivamente a função de achatar nele e concatene o resultado. Caso contrário, adicione o elemento.

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // ES2019+ oferece uma maneira muito mais simples: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Implementar uma Trie (Árvore de Prefixo)

Implemente uma estrutura de dados Trie com os métodos insert, search e startsWith.

Explicação: Uma Trie é uma estrutura de dados em forma de árvore usada para armazenar e recuperar chaves de forma eficiente em um conjunto de dados de strings. Cada nó representa um caractere, e os caminhos da raiz representam palavras ou prefixos.

class TrieNode { constructor() { this.children = {}; this.isEndOfWord = false; } } class Trie { constructor() { this.root = new TrieNode(); } insert(word) { let node = this.root; for (const char of word) { if (!node.children[char]) { node.children[char] = new TrieNode(); } node = node.children[char]; } node.isEndOfWord = true; } search(word) { let node = this.root; for (const char of word) { if (!node.children[char]) return false; node = node.children[char]; } return node.isEndOfWord; } startsWith(prefix) { let node = this.root; for (const char of prefix) { if (!node.children[char]) return false; node = node.children[char]; } return true; } } const trie = new Trie(); trie.insert('apple'); console.log(trie.search('apple')); // true console.log(trie.search('app')); // false console.log(trie.startsWith('app')); // true

Embaralhar um Array (Fisher-Yates)

Escreva uma função para embaralhar um array no local usando o algoritmo Fisher-Yates (ou Knuth).

Explicação: O algoritmo Fisher-Yates itera um array do último elemento para o primeiro. Em cada iteração, ele troca o elemento atual com um elemento escolhido aleatoriamente do início do array até o elemento atual (inclusive).

function shuffleArray(arr) { for (let i = arr.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [arr[i], arr[j]] = [arr[j], arr[i]]; // Trocar elementos } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // ex: [3, 5, 1, 2, 4]

Compor Funções

Implemente uma função compose que recebe múltiplas funções e retorna uma nova função que as aplica da direita para a esquerda.

Explicação: A composição de funções (f ∘ g)(x) = f(g(x)) aplica uma função ao resultado de outra. Uma função compose(f, g, h) geral significaria f(g(h(x))). Use reduceRight para uma implementação elegante.

function compose(...funcs) { return function(initialArg) { return funcs.reduceRight((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const composedFunc = compose(subtract2, multiply3, add5); console.log(composedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Pipe Functions

Implemente uma função pipe que recebe múltiplas funções e retorna uma nova função que as aplica da esquerda para a direita.

Explicação: Semelhante a compose, mas a ordem de aplicação é invertida: pipe(f, g, h) significa h(g(f(x))). Use reduce para a implementação.

function pipe(...funcs) { return function(initialArg) { return funcs.reduce((acc, func) => func(acc), initialArg); }; } const add5 = x => x + 5; const multiply3 = x => x * 3; const subtract2 = x => x - 2; const pipedFunc = pipe(add5, multiply3, subtract2); console.log(pipedFunc(10)); // (10 + 5) * 3 - 2 = 15 * 3 - 2 = 45 - 2 = 43

Problema da Troca de Moedas

Dado um array de denominações de moedas e um valor, encontre o número mínimo de moedas para fazer esse valor. Assuma um suprimento infinito de cada moeda.

Explicação: Este é um problema clássico de programação dinâmica. Crie um array dp onde dp[i] armazena o mínimo de moedas necessárias para o valor i. dp[i] = min(dp[i - moeda]) + 1 para todas as moedas.

function coinChange(coins, amount) { const dp = new Array(amount + 1).fill(Infinity); dp[0] = 0; for (let i = 1; i <= amount; i++) { for (const coin of coins) { if (i - coin >= 0) { dp[i] = Math.min(dp[i], dp[i - coin] + 1); } } } return dp[amount] === Infinity ? -1 : dp[amount]; } console.log(coinChange([1, 2, 5], 11)); // 3 (5 + 5 + 1) console.log(coinChange([2], 3)); // -1

Ancestral Comum Mais Baixo (BST)

Dada uma árvore de busca binária (BST), encontre o ancestral comum mais baixo (LCA) de dois nós dados na BST.

Explicação: O LCA é o nó mais profundo que tem ambos os nós dados como descendentes. Em uma BST, você pode encontrá-lo atravessando da raiz. Se ambos os nós forem menores que o nó atual, vá para a esquerda. Se ambos forem maiores, vá para a direita. Se um for menor e outro maior (ou um for o nó atual), então o nó atual é o LCA.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // LCA encontrado } } return null; // Não deve acontecer em uma BST válida com p e q } // Exemplo: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA é 6

Serializar e Desserializar Árvore Binária

Projete um algoritmo para serializar e desserializar uma árvore binária.

Explicação: A serialização converte uma árvore em uma string ou array. A desserialização reconstrói a árvore. Uma maneira comum é a Travessia em Pré-ordem (DFS). Use um marcador especial (ex: '#') para nós nulos para preservar a estrutura.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function serialize(root) { const values = []; function dfs(node) { if (!node) { values.push('#'); return; } values.push(node.val); dfs(node.left); dfs(node.right); } dfs(root); return values.join(','); } function deserialize(data) { const values = data.split(','); let index = 0; function buildTree() { if (values[index] === '#') { index++; return null; } const node = new Node(parseInt(values[index])); index++; node.left = buildTree(); node.right = buildTree(); return node; } return buildTree(); } // Exemplo: 1 -> [2, 3] -> [null, null, 4, 5] // Serializado: '1,2,#,#,3,4,#,#,5,#,#'

Implementar `setInterval` com `setTimeout`

Implemente uma função mySetInterval que imita setInterval mas usa setTimeout recursivamente.

Explicação: setInterval executa uma função repetidamente a cada N milissegundos. Você pode conseguir isso fazendo uma função chamar a si mesma com setTimeout após cada execução. Você também precisa de uma maneira de limpá-lo (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // Agendar a próxima chamada }, delay); } run(); return interval; // Retornar um objeto para permitir a limpeza } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Exemplo de uso: let count = 0; const intervalId = mySetInterval(() => { console.log(`Olá: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

Travessia de Grafo (BFS & DFS)

Implemente Busca em Largura (BFS) e Busca em Profundidade (DFS) para um grafo dado (representação de lista de adjacência).

Explicação: BFS explora os vizinhos primeiro (usando uma Fila), enquanto DFS explora o mais longe possível ao longo de cada ramificação (usando uma Pilha ou recursão). Mantenha o controle dos nós visitados para evitar ciclos e trabalho redundante.

const graph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: ['F'], F: [] }; function bfs(graph, startNode) { const queue = [startNode]; const visited = new Set(); const result = []; visited.add(startNode); while (queue.length > 0) { const node = queue.shift(); result.push(node); for (const neighbor of graph[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } return result; } function dfs(graph, startNode) { const stack = [startNode]; const visited = new Set(); const result = []; while (stack.length > 0) { const node = stack.pop(); if (!visited.has(node)) { visited.add(node); result.push(node); // Adicionar vizinhos em ordem inversa para processá-los em ordem mais tarde (opcional) for (let i = graph[node].length - 1; i >= 0; i--) { stack.push(graph[node][i]); } } } return result; } console.log('BFS:', bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F'] console.log('DFS:', dfs(graph, 'A')); // ['A', 'B', 'D', 'C', 'E', 'F']

Rotacionar Imagem (Matriz)

Você recebe uma matriz 2D n x n representando uma imagem. Rotacione a imagem em 90 graus (sentido horário) no local.

Explicação: Uma maneira comum de conseguir isso é primeiro transpor a matriz (trocando matrix[i][j] com matrix[j][i]) e depois inverter cada linha.

function rotate(matrix) { const n = matrix.length; // Transpor for (let i = 0; i < n; i++) { for (let j = i; j < n; j++) { [matrix[i][j], matrix[j][i]] = [matrix[j][i], matrix[i][j]]; } } // Inverter cada linha for (let i = 0; i < n; i++) { matrix[i].reverse(); } return matrix; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(rotate(matrix)); // [[7, 4, 1], [8, 5, 2], [9, 6, 3]]

Travessia de Matriz Espiral

Dada uma matriz m x n, retorne todos os elementos da matriz em ordem espiral.

Explicação: Use quatro ponteiros para definir os limites: top, bottom, left, right. Percorra a linha superior, depois a coluna direita, depois a linha inferior, depois a coluna esquerda, diminuindo os limites após cada travessia, até que left cruze right ou top cruze bottom.

function spiralOrder(matrix) { if (!matrix || matrix.length === 0) return []; const rows = matrix.length, cols = matrix[0].length; const result = []; let top = 0, bottom = rows - 1, left = 0, right = cols - 1; while (top <= bottom && left <= right) { for (let i = left; i <= right; i++) result.push(matrix[top][i]); top++; for (let i = top; i <= bottom; i++) result.push(matrix[i][right]); right--; if (top <= bottom) { for (let i = right; i >= left; i--) result.push(matrix[bottom][i]); bottom--; } if (left <= right) { for (let i = bottom; i >= top; i--) result.push(matrix[i][left]); left++; } } return result; } const matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]; console.log(spiralOrder(matrix)); // [1, 2, 3, 6, 9, 8, 7, 4, 5]

Zerar Matriz

Dada uma matriz m x n, se um elemento for 0, defina sua linha e coluna inteiras como 0. Faça isso no local.

Explicação: Uma abordagem ingênua requer espaço extra O(m*n). Para fazê-lo em espaço O(1) (ou O(m+n)), você pode usar a primeira linha e a primeira coluna para armazenar informações sobre quais linhas/colunas precisam ser zeradas. Você precisa de flags separadas para saber se a própria primeira linha/coluna precisa ser zerada.

function setZeroes(matrix) { const rows = matrix.length, cols = matrix[0].length; let firstColZero = false; for (let r = 0; r < rows; r++) { if (matrix[r][0] === 0) firstColZero = true; for (let c = 1; c < cols; c++) { if (matrix[r][c] === 0) { matrix[r][0] = 0; matrix[0][c] = 0; } } } for (let r = rows - 1; r >= 0; r--) { for (let c = cols - 1; c >= 1; c--) { if (matrix[r][0] === 0 || matrix[0][c] === 0) { matrix[r][c] = 0; } } if (firstColZero) matrix[r][0] = 0; } return matrix; } // Exemplo: [[1,1,1],[1,0,1],[1,1,1]] => [[1,0,1],[0,0,0],[1,0,1]]

Implementar `Promise.race`

Implemente uma função que se comporta como Promise.race.

Explicação: Promise.race recebe um array de promessas e retorna uma única promessa. Esta promessa se resolve (cumpre ou rejeita) assim que qualquer uma das promessas de entrada se resolve, com o valor ou motivo dessa promessa.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // Ou resolve/reject dependendo da especificação } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); }); } const p1 = new Promise((resolve) => setTimeout(resolve, 500, 'one')); const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'two')); myPromiseRace([p1, p2]).then(value => console.log(value)); // 'two'

Padrão Singleton

Implemente o padrão de design Singleton em JavaScript.

Explicação: O padrão Singleton garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Isso pode ser alcançado usando um closure para manter a instância.

const Singleton = (function() { let instance; function createInstance() { // Métodos e variáveis privadas const privateVar = 'Eu sou privada'; function privateMethod() { console.log('Privado'); } return { // Métodos e variáveis públicas publicVar: 'Eu sou pública', publicMethod: function() { console.log('Público'); }, getPrivate: function() { return privateVar; } }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true console.log(instance1.getPrivate()); // 'Eu sou privada'

Validar Endereço IP

Escreva uma função para verificar se uma determinada string é um endereço IPv4 ou IPv6 válido.

Explicação: Para IPv4, verifique 4 números (0-255) separados por pontos, sem zeros à esquerda (exceto para '0' em si). Para IPv6, verifique 8 grupos de 1-4 dígitos hexadecimais separados por dois pontos (lidar com variações como '::'). Regex pode ser usado, mas a análise manual é muitas vezes mais clara para entrevistas.

function validIPAddress(IP) { function isIPv4(s) { const parts = s.split('.'); if (parts.length !== 4) return false; for (const part of parts) { if (!/^[0-9]+$/.test(part)) return false; if (part.length > 1 && part.startsWith('0')) return false; const num = parseInt(part); if (num < 0 || num > 255) return false; } return true; } function isIPv6(s) { const parts = s.split(':'); if (parts.length !== 8) return false; // Simplificado: Sem '::' for (const part of parts) { if (part.length < 1 || part.length > 4) return false; if (!/^[0-9a-fA-F]+$/.test(part)) return false; } return true; } if (IP.includes('.')) return isIPv4(IP) ? 'IPv4' : 'Neither'; if (IP.includes(':')) return isIPv6(IP) ? 'IPv6' : 'Neither'; return 'Neither'; } console.log(validIPAddress('172.16.254.1')); // IPv4 console.log(validIPAddress('2001:0db8:85a3:0000:0000:8a2e:0370:7334')); // IPv6 (Simplificado) console.log(validIPAddress('256.256.256.256')); // Nenhum

Encontrar Elemento de Pico

Um elemento de pico é um elemento que é estritamente maior que seus vizinhos. Dado um array de entrada nums, encontre um elemento de pico e retorne seu índice.

Explicação: Como qualquer pico serve, e nums[-1] e nums[n] são considerados -Infinity, você pode usar uma busca binária modificada. Se nums[mid] for menor que nums[mid+1], um pico deve existir à direita. Caso contrário, um pico deve existir à esquerda (ou mid é um pico).

function findPeakElement(nums) { let left = 0; let right = nums.length - 1; while (left < right) { let mid = Math.floor((left + right) / 2); if (nums[mid] < nums[mid + 1]) { left = mid + 1; // O pico está à direita } else { right = mid; // O pico é mid ou está à esquerda } } return left; // 'left' será o índice de um pico } console.log(findPeakElement([1, 2, 3, 1])); // 2 (índice de 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (índice de 6) ou 1 (índice de 2)

Contagem de Bits

Dado um número inteiro n, retorne um array ans de comprimento n + 1 de modo que para cada i (0 <= i <= n), ans[i] seja o número de 1s na representação binária de i.

Explicação: Você pode resolver isso usando programação dinâmica. Observe a relação: dp[i] = dp[i >> 1] + (i & 1). O número de 1s em i é o número de 1s em i deslocado para a direita (ou seja, i/2), mais 1 se i for ímpar.

function countBits(n) { const dp = new Array(n + 1).fill(0); for (let i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); // Ou: dp[i] = dp[i & (i - 1)] + 1; (Remove o último bit definido) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

Potência de Dois

Dado um número inteiro n, retorne true se for uma potência de dois. Caso contrário, retorne false.

Explicação: Uma potência de dois em representação binária tem exatamente um bit '1' (ex: 1=1, 2=10, 4=100, 8=1000). Um truque bitwise inteligente é verificar se n > 0 e (n & (n - 1)) === 0. Se n é uma potência de dois, n-1 terá todos os bits abaixo desse '1' definidos como '1'. A operação AND entre eles resulta em 0.

function isPowerOfTwo(n) { return n > 0 && (n & (n - 1)) === 0; } console.log(isPowerOfTwo(16)); // true console.log(isPowerOfTwo(1)); // true console.log(isPowerOfTwo(18)); // false

Mesclar Intervalos

Dado um array de intervals onde intervals[i] = [starti, endi], mescle todos os intervalos sobrepostos e retorne um array dos intervalos não sobrepostos.

Explicação: Primeiro, classifique os intervalos com base em seus tempos de início. Em seguida, itere pelos intervalos classificados. Se o intervalo atual se sobrepõe ao último intervalo na lista de resultados, mescle-os atualizando o tempo de término do último intervalo. Caso contrário, adicione o intervalo atual à lista de resultados.

function mergeIntervals(intervals) { if (intervals.length === 0) return []; intervals.sort((a, b) => a[0] - b[0]); const merged = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const current = intervals[i]; if (current[0] <= last[1]) { // Sobreposição: mesclar last[1] = Math.max(last[1], current[1]); } else { // Sem sobreposição: adicionar novo intervalo merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

Quebra de Palavras

Dada uma string s e um dicionário de strings wordDict, retorne true se s puder ser segmentada em uma sequência separada por espaços de uma ou mais palavras do dicionário.

Explicação: Use programação dinâmica. Crie um array booleano dp onde dp[i] é verdadeiro se a substring s[0...i-1] puder ser segmentada. dp[i] é verdadeiro se existir um j < i tal que dp[j] seja verdadeiro e a substring s[j...i-1] esteja no dicionário.

function wordBreak(s, wordDict) { const wordSet = new Set(wordDict); const n = s.length; const dp = new Array(n + 1).fill(false); dp[0] = true; // Caso base: string vazia for (let i = 1; i <= n; i++) { for (let j = 0; j < i; j++) { if (dp[j] && wordSet.has(s.substring(j, i))) { dp[i] = true; break; } } } return dp[n]; } console.log(wordBreak('leetcode', ['leet', 'code'])); // true console.log(wordBreak('applepenapple', ['apple', 'pen'])); // true console.log(wordBreak('catsandog', ['cats', 'dog', 'sand', 'and', 'cat'])); // false

Implementar `Array.prototype.flat`

Implemente sua própria versão de Array.prototype.flat, que cria um novo array com todos os elementos de sub-array concatenados recursivamente até uma profundidade especificada.

Explicação: Use recursão. Itere pelo array. Se um elemento for um array e a profundidade atual for menor que a profundidade especificada, chame recursivamente a função de achatamento nele. Caso contrário, adicione o elemento. Lide com a profundidade padrão de 1.

function myFlat(arr, depth = 1) { let flattened = []; arr.forEach(item => { if (Array.isArray(item) && depth > 0) { flattened.push(...myFlat(item, depth - 1)); } else { flattened.push(item); } }); return flattened; } console.log(myFlat([1, [2, [3, 4], 5], 6])); // [2, [3, 4], 5] console.log(myFlat([1, [2, [3, 4], 5], 6], 2)); // [1, 2, 3, 4, 5, 6]

Inverter Palavras em uma String

Dada uma string de entrada s, inverta a ordem das palavras.

Explicação: Uma 'palavra' é definida como uma sequência de caracteres que não são espaços. Remova os espaços em branco do início e do fim da string, divida-a por um ou mais espaços, filtre quaisquer strings vazias resultantes de múltiplos espaços, inverta o array e junte-o novamente com espaços únicos.

function reverseWords(s) { return s.trim().split(/\s+/).reverse().join(' '); } console.log(reverseWords('o céu é azul')); // 'azul é céu o' console.log(reverseWords(' olá mundo ')); // 'mundo olá' console.log(reverseWords('um bom exemplo')); // 'exemplo bom um'

Analisador de Query String

Escreva uma função para analisar uma query string de URL em um objeto.

Explicação: Divida a string por &. Para cada parte, divida por = para obter a chave e o valor. Decodifique os componentes URI para a chave e o valor. Lide com casos em que uma chave pode aparecer várias vezes (crie um array) ou não tem valor.

function parseQueryString(query) { if (query.startsWith('?')) { query = query.substring(1); } const result = {}; if (!query) return result; query.split('&').forEach(pair => { const [key, value] = pair.split('='); const decodedKey = decodeURIComponent(key); const decodedValue = value !== undefined ? decodeURIComponent(value) : true; if (result.hasOwnProperty(decodedKey)) { if (Array.isArray(result[decodedKey])) { result[decodedKey].push(decodedValue); } else { result[decodedKey] = [result[decodedKey], decodedValue]; } } else { result[decodedKey] = decodedValue; } }); return result; } console.log(parseQueryString('?foo=bar&baz=qux&baz=quux&corge')); // { foo: 'bar', baz: ['qux', 'quux'], corge: true }

Usar `Proxy` para Validação

Demonstre como usar um objeto Proxy para validação de propriedade de objeto.

Explicação: Um Proxy permite interceptar e personalizar operações realizadas em um objeto. Use o 'trap' set para validar valores antes de atribuí-los a uma propriedade. Lance um erro se a validação falhar.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('A idade não é um número inteiro'); } if (value < 0 || value > 150) { throw new RangeError('A idade parece inválida'); } } // Comportamento padrão: Definir a propriedade obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // OK console.log(personProxy.age); // 30 // personProxy.age = 'trinta'; // Lança TypeError // personProxy.age = 200; // Lança RangeError

Salas de Reunião

Dado um array de intervalos de tempo de reunião [[start1, end1], [start2, end2], ...], determine se uma pessoa poderia participar de todas as reuniões.

Explicação: Se uma pessoa pode participar de todas as reuniões, isso significa que nenhuma reunião se sobrepõe. Para verificar isso, ordene os intervalos por seus horários de início. Em seguida, itere pelos intervalos ordenados e verifique se o horário de início da reunião atual é anterior ao horário de término da reunião anterior. Se for, há uma sobreposição e a pessoa não pode participar de todas.

function canAttendMeetings(intervals) { if (intervals.length < 2) return true; intervals.sort((a, b) => a[0] - b[0]); for (let i = 1; i < intervals.length; i++) { if (intervals[i][0] < intervals[i - 1][1]) { return false; // Sobreposição detectada } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // false (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // true

Implementar `Promise.any`

Implemente uma função que se comporta como Promise.any.

Explicação: Promise.any recebe um array de promessas e retorna uma única promessa. Esta promessa é cumprida assim que qualquer uma das promessas de entrada é cumprida. Se todas as promessas de entrada forem rejeitadas, ela é rejeitada com um AggregateError contendo todos os motivos de rejeição.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'Todas as promessas foram rejeitadas')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Resolver assim que um cumprir .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'Todas as promessas foram rejeitadas')); } }); }); }); } // Exemplo: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Exemplo: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

Padrão Observer

Implemente o padrão de design Observer (Pub/Sub) em JavaScript.

Explicação: O padrão Observer define uma dependência um-para-muitos entre objetos. Quando um objeto (Subject) muda de estado, todos os seus dependentes (Observers) são notificados e atualizados automaticamente. O Subject mantém uma lista de observadores e fornece métodos para adicioná-los, removê-los e notificá-los.

class Subject { constructor() { this.observers = []; } subscribe(observer) { this.observers.push(observer); } unsubscribe(observer) { this.observers = this.observers.filter(obs => obs !== observer); } notify(data) { this.observers.forEach(observer => observer.update(data)); } } class Observer { constructor(name) { this.name = name; } update(data) { console.log(`${this.name} recebeu: ${data}`); } } const subject = new Subject(); const obs1 = new Observer('Observador 1'); const obs2 = new Observer('Observador 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('Algo aconteceu!'); // Observador 1 recebeu: Algo aconteceu! // Observador 2 recebeu: Algo aconteceu!

Encontrar Número Duplicado

Dado um array de inteiros nums contendo n + 1 inteiros onde cada inteiro está no intervalo [1, n] inclusive, encontre o número repetido.

Explicação: Como os números estão no intervalo [1, n] e o tamanho do array é n+1, isso garante pelo menos um duplicado. Isso pode ser mapeado para um problema de 'Encontrar Ciclo em Lista Encadeada' (Tartaruga e Lebre de Floyd). Trate os valores do array como ponteiros: índice -> nums[índice].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Fase 1: Encontre o ponto de interseção do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Fase 2: Encontre a entrada para o ciclo let ptr1 = nums[0]; let ptr2 = tortoise; while (ptr1 !== ptr2) { ptr1 = nums[ptr1]; ptr2 = nums[ptr2]; } return ptr1; } console.log(findDuplicate([1, 3, 4, 2, 2])); // 2 console.log(findDuplicate([3, 1, 3, 4, 2])); // 3

Sanitizador Básico de HTML

Escreva uma função básica para sanitizar uma string HTML, removendo tags potencialmente prejudiciais (como <script>) enquanto mantém tags seguras (como <p>, <b>).

Explicação: Este é um tópico complexo, e os sanitizadores do mundo real são sofisticados. Para uma entrevista, uma abordagem básica pode envolver o uso de Regex para remover tags específicas ou permitir apenas uma lista branca de tags. Isso não é seguro para produção.

function basicSanitize(html) { // AVISO: Este é um exemplo muito básico e inseguro. // NÃO use isso em produção. Use uma biblioteca como DOMPurify. // Remover tags de script let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Exemplo: Remover atributos onclick (muito básico) sanitized = sanitized.replace(/\s(on\w+)=['"]?[^'"]*['"]?/gi, ''); return sanitized; } console.log(basicSanitize('<p>Olá <script>alert("XSS")</script> <b onclick="danger()">Mundo</b></p>')); // <p>Olá <b >Mundo</b></p>

Distância de Edição

Dadas duas strings word1 e word2, retorne o número mínimo de operações (inserir, excluir ou substituir) necessárias para converter word1 em word2.

Explicação: Este é um problema clássico de programação dinâmica (distância de Levenshtein). Crie um array 2D dp onde dp[i][j] é a distância de edição entre os primeiros i caracteres de word1 e os primeiros j caracteres de word2. A relação de recorrência envolve considerar o custo de inserção, exclusão e substituição.

function minDistance(word1, word2) { const m = word1.length, n = word2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = word1[i - 1] === word2[j - 1] ? 0 : 1; dp[i][j] = Math.min( dp[i - 1][j] + 1, // Exclusão dp[i][j - 1] + 1, // Inserção dp[i - 1][j - 1] + cost // Substituição/Correspondência ); } } return dp[m][n]; } console.log(minDistance('cavalo', 'ros')); // 3 console.log(minDistance('intenção', 'execução')); // 5

Subsequência Crescente Mais Longa (LIS)

Dado um array de inteiros nums, retorne o comprimento da subsequência estritamente crescente mais longa.

Explicação: Use programação dinâmica. Seja dp[i] o comprimento da LIS terminando no índice i. Para calcular dp[i], encontre todos os j < i tais que nums[j] < nums[i], e tome dp[i] = 1 + max(dp[j]). A resposta final é o valor máximo no array dp.

function lengthOfLIS(nums) { if (nums.length === 0) return 0; const n = nums.length; const dp = new Array(n).fill(1); let maxLength = 1; for (let i = 1; i < n; i++) { for (let j = 0; j < i; j++) { if (nums[i] > nums[j]) { dp[i] = Math.max(dp[i], dp[j] + 1); } } maxLength = Math.max(maxLength, dp[i]); } return maxLength; } console.log(lengthOfLIS([10, 9, 2, 5, 3, 7, 101, 18])); // 4 (2, 3, 7, 101)

Problema das N-Rainhas

O quebra-cabeça das N-Rainhas é o problema de colocar N rainhas de xadrez em um tabuleiro N×N de forma que nenhuma das duas rainhas se ameacem. Dado N, retorne uma solução válida (ou todas).

Explicação: Use retrocesso. Coloque as rainhas linha por linha. Para cada linha, tente colocar uma rainha em cada coluna. Antes de colocar, verifique se a posição proposta é segura (não é atacada por rainhas em linhas anteriores). Se for seguro, coloque-a e recorra para a próxima linha. Se a recursão falhar, retroceda (remova a rainha) e tente a próxima coluna.

function solveNQueens(n) { const results = []; const board = Array(n).fill('.').map(() => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < row; i++) if (board[i][col] === 'Q') return false; // Verificar coluna for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Verificar diagonal superior-esquerda for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Verificar diagonal superior-direita return true; } function backtrack(row) { if (row === n) { results.push(board.map(r => r.join(''))); return; } for (let col = 0; col < n; col++) { if (isSafe(row, col)) { board[row][col] = 'Q'; backtrack(row + 1); board[row][col] = '.'; // Retroceder } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

Usar `WeakMap` para Dados Privados

Demonstre como usar WeakMap para armazenar dados privados para instâncias de classe.

Explicação: WeakMap permite associar dados a um objeto de uma forma que não impede a coleta de lixo se o objeto não for mais referenciado. Isso é útil para criar membros 'privados' em classes JavaScript antes que os campos privados nativos estivessem amplamente disponíveis ou para casos de uso específicos.

const privateData = new WeakMap(); class Person { constructor(name, age) { privateData.set(this, { name: name, age: age }); } getName() { return privateData.get(this).name; } getAge() { return privateData.get(this).age; } celebrateBirthday() { privateData.get(this).age++; } } const person = new Person('Alice', 30); console.log(person.getName()); // Alice person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // undefined - 'name' não é uma propriedade pública // console.log(privateData.get(person)); // Acessível, mas não diretamente via 'person.'

Implementar `Promise.allSettled`

Implemente uma função que se comporta como Promise.allSettled.

Explicação: Promise.allSettled recebe um array de promessas e retorna uma única promessa. Esta promessa é cumprida quando todas as promessas de entrada foram resolvidas (seja cumpridas ou rejeitadas). O valor de cumprimento é um array de objetos, cada um descrevendo o resultado de uma promessa ({status: 'fulfilled', value: ...} ou {status: 'rejected', reason: ...}).

function myPromiseAllSettled(promises) { return new Promise((resolve) => { const numPromises = promises.length; const results = []; let settledCount = 0; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = { status: 'fulfilled', value: value }; }) .catch(reason => { results[index] = { status: 'rejected', reason: reason }; }) .finally(() => { settledCount++; if (settledCount === numPromises) { resolve(results); } }); }); }); } // Exemplo: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

Encontrar a Mediana de Dois Arrays Ordenados

Dados dois arrays ordenados nums1 e nums2 de tamanho m e n respectivamente, retorne a mediana dos dois arrays ordenados.

Explicação: Este é um problema difícil frequentemente resolvido com uma abordagem de busca binária no array menor. O objetivo é particionar ambos os arrays de forma que todos os elementos no lado esquerdo sejam menores ou iguais a todos os elementos no lado direito, e o número de elementos no lado esquerdo seja igual (ou um a mais do que) o lado direito. A mediana pode então ser calculada a partir dos elementos de contorno.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Garantir que nums1 seja menor const m = nums1.length, n = nums2.length; let low = 0, high = m; while (low <= high) { const partitionX = Math.floor((low + high) / 2); const partitionY = Math.floor((m + n + 1) / 2) - partitionX; const maxX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1]; const minX = (partitionX === m) ? Infinity : nums1[partitionX]; const maxY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1]; const minY = (partitionY === n) ? Infinity : nums2[partitionY]; if (maxX <= minY && maxY <= minX) { if ((m + n) % 2 === 0) { return (Math.max(maxX, maxY) + Math.min(minX, minY)) / 2; } else { return Math.max(maxX, maxY); } } else if (maxX > minY) { high = partitionX - 1; } else { low = partitionX + 1; } } throw new Error('Arrays de entrada não estão ordenados'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

Resolvedor de Sudoku

Escreva um programa para resolver um quebra-cabeça de Sudoku preenchendo as células vazias.

Explicação: Use retrocesso. Encontre uma célula vazia. Tente preenchê-la com números de 1 a 9. Para cada número, verifique se é válido (não viola as regras do Sudoku). Se for válido, chame recursivamente o resolvedor. Se a recursão retornar true, uma solução foi encontrada. Se não, retroceda (redefina a célula) e tente o próximo número.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Verificar linha if (board[i][col] === numStr) return false; // Verificar coluna const boxRow = 3 * Math.floor(row / 3) + Math.floor(i / 3); const boxCol = 3 * Math.floor(col / 3) + i % 3; if (board[boxRow][boxCol] === numStr) return false; // Verificar caixa } return true; } function backtrack() { for (let r = 0; r < 9; r++) { for (let c = 0; c < 9; c++) { if (board[r][c] === '.') { for (let n = 1; n <= 9; n++) { const numStr = String(n); if (isValid(r, c, numStr)) { board[r][c] = numStr; if (backtrack()) return true; board[r][c] = '.'; // Retroceder } } return false; // Nenhum número válido encontrado } } } return true; // Tabuleiro resolvido } backtrack(); return board; } // O exemplo requer um tabuleiro 9x9 com '.' para células vazias.

Implementar um Padrão Básico de Middleware

Implemente um padrão simples de middleware frequentemente visto em frameworks web.

Explicação: Funções de middleware processam uma requisição antes que ela atinja o manipulador final. Cada middleware pode modificar a requisição/resposta ou passar o controle para o próximo middleware. Crie uma classe ou objeto que gerencia uma lista de funções de middleware e as executa em sequência.

class App { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } handle(request) { let index = 0; const next = () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; middleware(request, next); } }; next(); } } const app = new App(); app.use((req, next) => { console.log('1: Registrando...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Autenticando...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Manipulador Final:', req); }); app.handle({ data: 'alguma requisição' }); // 1: Registrando... // 2: Autenticando... // 3: Manipulador Final: { data: 'alguma requisição', logged: true, authed: true }

Detectar Ciclo em Grafo Direcionado

Dado um grafo direcionado, escreva uma função para determinar se ele contém um ciclo.

Explicação: Use Busca em Profundidade (DFS). Mantenha dois conjuntos: visiting (nós atualmente na pilha de recursão) e visited (nós que foram totalmente explorados). Se você encontrar um nó no conjunto visiting durante o DFS, você encontrou uma aresta de retorno, o que significa que há um ciclo.

function hasCycleDirected(graph) { const visiting = new Set(); const visited = new Set(); const nodes = Object.keys(graph); function dfs(node) { visiting.add(node); for (const neighbor of graph[node]) { if (visiting.has(neighbor)) return true; // Ciclo detectado if (!visited.has(neighbor)) { if (dfs(neighbor)) return true; } } visiting.delete(node); visited.add(node); return false; } for (const node of nodes) { if (!visited.has(node)) { if (dfs(node)) return true; } } return false; } const cyclicGraph = { A: ['B'], B: ['C'], C: ['A'] }; const acyclicGraph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: [] }; console.log(hasCycleDirected(cyclicGraph)); // true console.log(hasCycleDirected(acyclicGraph)); // false

Implementar o comportamento de `Object.freeze`

Explique Object.freeze e implemente uma versão (rasa).

Explicação: Object.freeze impede a adição de novas propriedades, a remoção de propriedades existentes e a alteração de propriedades existentes ou sua enumerabilidade, configurabilidade ou gravabilidade. Ele torna um objeto imutável (rasamente). Você pode implementá-lo usando Object.preventExtensions e Object.defineProperty para tornar as propriedades existentes não graváveis e não configuráveis.

function myFreeze(obj) { Object.preventExtensions(obj); // Impede a adição de novas propriedades Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { writable: false, configurable: false }); }); return obj; } const obj = { a: 1, b: 2 }; myFreeze(obj); // obj.c = 3; // Falha silenciosamente (ou lança erro em modo estrito) // delete obj.a; // Falha silenciosamente (ou lança erro em modo estrito) // obj.a = 10; // Falha silenciosamente (ou lança erro em modo estrito) console.log(obj); // { a: 1, b: 2 }

Usar `requestAnimationFrame` para Animação Suave

Explique requestAnimationFrame e mostre como usá-lo para um loop de animação simples.

Explicação: requestAnimationFrame (rAF) informa ao navegador que você deseja realizar uma animação e solicita que o navegador chame uma função especificada para atualizar uma animação antes do próximo repaint. É mais eficiente e suave do que usar setTimeout ou setInterval para animações, pois sincroniza com a taxa de atualização do navegador e pausa quando a guia não está visível.

// HTML: <div id='box' style='width: 50px; height: 50px; background: red; position: absolute; left: 0px;'></div> const box = document.getElementById('box'); let position = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; const elapsedTime = currentTime - startTime; // Mover 100px em 2 segundos (50px/seg) position = (elapsedTime / 2000) * 100; if (position < 200) { // Continuar animando até atingir 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Iniciar a animação // requestAnimationFrame(animate); // Descomente para executar no navegador console.log('Use requestAnimationFrame para animações de navegador.');

Implementar `Array.prototype.some`

Implemente sua própria versão de Array.prototype.some.

Explicação: some testa se pelo menos um elemento no array passa no teste implementado pela função fornecida. Ele retorna true se encontrar um elemento para o qual o callback retorna true; caso contrário, retorna false.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // Lidar com arrays esparsos if (callback.call(thisArg, arr[i], i, arr)) { return true; } } } return false; } const arr = [1, 2, 3, 4, 5]; const hasEven = mySome(arr, x => x % 2 === 0); console.log(hasEven); // true

Implementar `Array.prototype.every`

Implemente sua própria versão de Array.prototype.every.

Explicação: every testa se todos os elementos no array passam no teste implementado pela função fornecida. Ele retorna true se todos os elementos passarem (ou se o array estiver vazio) e false se algum elemento falhar.

function myEvery(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (!callback.call(thisArg, arr[i], i, arr)) { return false; } } } return true; } const arr1 = [1, 30, 39, 29, 10, 13]; const isBelow40 = myEvery(arr1, x => x < 40); console.log(isBelow40); // true const arr2 = [1, 30, 39, 29, 10, 45]; const isBelow40_2 = myEvery(arr2, x => x < 40); console.log(isBelow40_2); // false

Implementar `Array.prototype.findIndex`

Implemente sua própria versão de Array.prototype.findIndex.

Explicação: findIndex retorna o índice do primeiro elemento no array que satisfaz a função de teste fornecida. Caso contrário, retorna -1, indicando que nenhum elemento passou no teste.

function myFindIndex(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { if (callback.call(thisArg, arr[i], i, arr)) { return i; } } } return -1; } const arr = [5, 12, 8, 130, 44]; const isLargeNumber = (element) => element > 13; console.log(myFindIndex(arr, isLargeNumber)); // 3

O que são Web Workers?

Explique o que são Web Workers e seu principal caso de uso.

Explicação: Web Workers são um meio para o conteúdo da web executar scripts em threads em segundo plano. O principal caso de uso é descarregar tarefas demoradas ou computacionalmente intensivas da thread principal (que lida com atualizações de UI e interações do usuário), evitando assim que a UI se torne sem resposta ou 'congele'. Workers se comunicam com a thread principal via passagem de mensagens (postMessage e onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Enviar dados para o worker myWorker.onmessage = function(e) { console.log('Resultado do worker:', e.data); } } else { console.log('Seu navegador não suporta Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Mensagem recebida do script principal'); const result = e.data[0] * e.data[1]; console.log('Postando o resultado de volta para o script principal'); self.postMessage(result); } */ console.log('Web Workers executam scripts em threads em segundo plano.');

Decodificar Maneiras

Uma mensagem contendo letras de A-Z pode ser codificada em números usando o mapeamento 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26. Dada uma string s contendo apenas dígitos, retorne o número de maneiras de decodificá-la.

Explicação: Use programação dinâmica. Seja dp[i] o número de maneiras de decodificar s[0...i-1]. dp[i] depende de dp[i-1] (se s[i-1] for um código válido de 1 dígito) e dp[i-2] (se s[i-2...i-1] for um código válido de 2 dígitos).

function numDecodings(s) { if (!s || s[0] === '0') return 0; const n = s.length; const dp = new Array(n + 1).fill(0); dp[0] = 1; dp[1] = 1; for (let i = 2; i <= n; i++) { const oneDigit = parseInt(s.substring(i - 1, i)); const twoDigits = parseInt(s.substring(i - 2, i)); if (oneDigit >= 1) { dp[i] += dp[i - 1]; } if (twoDigits >= 10 && twoDigits <= 26) { dp[i] += dp[i - 2]; } } return dp[n]; } console.log(numDecodings('12')); // 2 ('AB' ou 'L') console.log(numDecodings('226')); // 3 ('BBF', 'BZ', 'VF') console.log(numDecodings('06')); // 0

Adição Bitwise (sem `+`)

Escreva uma função para adicionar dois inteiros sem usar o operador +.

Explicação: Use operadores bitwise. A soma pode ser calculada como a ^ b (soma sem carry), e o carry pode ser calculado como (a & b) << 1. Repita esse processo até que o carry se torne 0.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // Calcular carry a = a ^ b; // Calcular soma sem carry b = carry; // Carry se torna o novo 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

Verificar se é Palíndromo (Número)

Dado um inteiro x, retorne true se x for um palíndromo e false caso contrário.

Explicação: Um número negativo não é um palíndromo. Converta o número para uma string e verifique se a string é um palíndromo (lê-se da mesma forma para frente e para trás). Alternativamente, inverta o número matematicamente e compare.

function isPalindromeNumber(x) { if (x < 0) return false; // Abordagem de string // return String(x) === String(x).split('').reverse().join(''); // Abordagem matemática let original = x; let reversed = 0; while (x > 0) { reversed = reversed * 10 + (x % 10); x = Math.floor(x / 10); } return original === reversed; } console.log(isPalindromeNumber(121)); // true console.log(isPalindromeNumber(-121)); // false console.log(isPalindromeNumber(10)); // false

Padrão Factory

Implemente o padrão de design Factory em JavaScript.

Explicação: O padrão Factory fornece uma interface para criar objetos em uma superclasse, mas permite que as subclasses alterem o tipo de objetos que serão criados. É útil para criar objetos sem especificar a classe exata.

class Car { constructor(options) { this.type = 'Carro'; this.doors = options.doors || 4; } } class Truck { constructor(options) { this.type = 'Caminhão'; this.bedSize = options.bedSize || 'longa'; } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'carro') { return new Car(options); } else if (options.vehicleType === 'caminhão') { return new Truck(options); } else { throw new Error('Tipo de veículo desconhecido'); } } } const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'carro', doors: 2 }); const truck = factory.createVehicle({ vehicleType: 'caminhão', bedSize: 'curta' }); console.log(car); // Car { type: 'Carro', doors: 2 } console.log(truck); // Truck { type: 'Caminhão', bedSize: 'curta' }

Explique `Symbol` e um Caso de Uso

Explique o que é Symbol em JavaScript e forneça um caso de uso, como a criação de propriedades 'privadas' ou chaves de objeto únicas.

Explicação: Symbol é um tipo de dado primitivo introduzido no ES6. Suas instâncias são únicas e imutáveis. Eles são frequentemente usados como chaves para propriedades de objeto para evitar colisões de nomes entre diferentes bibliotecas ou para criar propriedades pseudo-privadas (embora não sejam verdadeiramente privadas, não são acessíveis via iteração típica ou Object.keys).

const _privateName = Symbol('nome'); const _privateMethod = Symbol('saudar'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Olá, meu nome é ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Olá, meu nome é Bob console.log(Object.keys(p)); // [] - Símbolos não são incluídos console.log(p[_privateName]); // Bob (Acessível se você tiver o Símbolo)

Converter `arguments` para um Array Real

Como você pode converter o objeto arguments (disponível em funções não-arrow) em um array JavaScript real?

Explicação: O objeto arguments parece um array, mas não é; ele não possui métodos como map, filter, etc. Você pode convertê-lo usando:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (Sintaxe de Espalhamento ES6)
  3. Array.prototype.slice.call(arguments) (Forma antiga)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Usando Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Sintaxe de Espalhamento const args2 = [...arguments]; console.log('Usando Espalhamento:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Usando Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Usando Array.from: 10 // Usando Espalhamento: 10 // Usando Slice: 10