Live Coding

Check if an Object is Empty

How to check if a JavaScript object is empty?

Explanation: An object is empty if it doesn't have any own enumerable properties. We can use Object.keys(obj) which returns an array of a given object's own enumerable property names. If the length of this array is 0, the object is empty.

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

Reverse a String

Write a function to reverse a given string.

Explanation: The simplest way is to convert the string into an array of characters, use the built-in reverse() method for arrays, and then join the characters back into a string.

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

Palindrome Check

Write a function that checks if a given string is a palindrome.

Explanation: A palindrome is a word or phrase that reads the same forwards and backward. We can check this by reversing the string (ignoring case and non-alphanumeric characters for a more robust check, but here we do a simple check) and comparing it to the original.

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

Find the Maximum Number in an Array

Write a function to find the largest number in an array of numbers.

Explanation: You can iterate through the array, keeping track of the largest number found so far. Alternatively, you can use the Math.max() function along with the spread syntax (...) to pass array elements as arguments.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // Or throw an error return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Write a program that prints numbers from 1 to n. But for multiples of three, print 'Fizz' instead of the number and for the multiples of five, print 'Buzz'. For numbers which are multiples of both three and five, print 'FizzBuzz'.

Explanation: This classic problem tests basic loop and conditional logic. You need to iterate from 1 to n and use the modulo operator (%) to check for divisibility by 3 and 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);

Remove Duplicates from an Array

Write a function that removes duplicate elements from an array.

Explanation: A modern and concise way to achieve this is by using a Set. Sets only store unique values. You can convert the array to a Set and then back to an array.

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

Anagram Check

Write a function to check if two strings are anagrams of each other.

Explanation: Anagrams are words formed by rearranging the letters of another. To check, we can clean, sort, and compare the strings. Cleaning involves removing non-alphanumeric characters and converting to a consistent case.

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

Calculate Factorial

Write a function to calculate the factorial of a non-negative integer.

Explanation: The factorial (n!) of a number is the product of all positive integers less than or equal to n. We can calculate this iteratively by multiplying numbers from 1 up to n.

function factorial(n) { if (n < 0) return undefined; // Factorial is not defined for negative numbers if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Sum All Numbers in an Array

Write a function that returns the sum of all numbers in an array.

Explanation: You can use the reduce method, which is ideal for accumulating a single value from an array. It takes a callback function and an initial value (0 for summation).

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

Flatten a Nested Array

Write a function to flatten a nested array. For simplicity, assume only one level of nesting.

Explanation: For a single level of nesting, you can use Array.prototype.concat() with the spread operator or the flat() method (ES2019).

function flattenArray(arr) { // Using flat() (simpler if ES2019+ is okay) // return arr.flat(); // Using reduce and concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Count Vowels in a String

Write a function that counts the number of vowels (a, e, i, o, u) in a given string.

Explanation: Iterate through the string (case-insensitively) and check if each character is a vowel. Maintain a counter.

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

Title Case a Sentence

Write a function that converts a sentence into title case (each word's first letter capitalized).

Explanation: Split the sentence into words, then iterate through each word, capitalizing the first letter and making the rest lowercase. Finally, join the words back.

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'

Two Sum Problem

Given an array of integers nums and an integer target, return indices of the two numbers such that they add up to target.

Explanation: A common approach is to use a hash map (or a JavaScript object) to store numbers and their indices as you iterate. For each number, check if target - current_number exists in the map.

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 []; // Or null, or throw error if no solution } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Implement a Counter using Closures

Create a function that returns a counter function. Each time the returned function is called, it should increment and return a count.

Explanation: This demonstrates closures. The inner function has access to the count variable of the outer function's scope, even after the outer function has finished executing.

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

Find the First Non-Repeating Character

Write a function that finds the first non-repeating character in a string.

Explanation: You can use a hash map to count character frequencies. First, iterate through the string to build the frequency map. Then, iterate through the string again and return the first character with a count of 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; // Or some indicator if all repeat } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Array Chunking

Write a function that splits an array into groups of a specified size.

Explanation: Iterate through the array, taking slices of the specified size and pushing them into a new array. Handle the case where the last chunk might be smaller.

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]]

Check if a Number is Prime

Write a function to determine if a given number is a prime number.

Explanation: A prime number is a natural number greater than 1 that has no positive divisors other than 1 and itself. Iterate from 2 up to the square root of the number, checking for divisibility. Handle edge cases like 1 and 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

Find the Longest Word in a String

Write a function that finds the longest word in a sentence.

Explanation: Split the string into an array of words. Then, iterate through the array, keeping track of the longest word found so far (or its length).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Clean words if needed (e.g., remove punctuation) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

Implement `Array.prototype.map`

Implement your own version of the Array.prototype.map function.

Explanation: The map function takes a callback and creates a new array by applying the callback to each element of the original array. Your function should iterate through the array and build the new 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]

Implement `Array.prototype.filter`

Implement your own version of the Array.prototype.filter function.

Explanation: The filter function takes a callback and creates a new array with all elements that pass the test implemented by the provided callback function.

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']

Implement `Array.prototype.reduce`

Implement your own version of the Array.prototype.reduce function.

Explanation: The reduce function executes a user-supplied 'reducer' callback function on each element of the array, passing in the return value from the calculation on the preceding element. The final result of running the reducer across all elements of the array is a single value. Handle the initial value argument.

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

Memoization - Fibonacci Sequence

Implement a Fibonacci function using memoization to optimize performance.

Explanation: The Fibonacci sequence involves repeated calculations. Memoization stores the results of expensive function calls and returns the cached result when the same inputs occur again.

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 (much faster than non-memoized)

Check for Balanced Brackets

Write a function to check if a string containing brackets {}[]() is balanced.

Explanation: Use a stack. When you encounter an opening bracket, push it onto the stack. When you encounter a closing bracket, check if the stack is empty or if the top element is the matching opening bracket. If it matches, pop the stack. If not, or if the stack is empty, it's unbalanced. At the end, an empty stack means balanced.

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

Implement a Simple Queue

Implement a Queue data structure with enqueue (add to back) and dequeue (remove from front) methods.

Explanation: A queue follows the First-In, First-Out (FIFO) principle. An array can be used, with push for enqueue and shift for 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

Implement a Simple Stack

Implement a Stack data structure with push (add to top) and pop (remove from top) methods.

Explanation: A stack follows the Last-In, First-Out (LIFO) principle. An array can be used, with push and pop methods.

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

Find the Missing Number in Sequence

Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array.

Explanation: The sum of numbers from 0 to n can be calculated using the formula n*(n+1)/2. The actual sum of the array elements can be calculated. The difference between these two sums will be the missing number.

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

Debounce Function

Implement a debounce function. Debouncing ensures that a function is not called again until a certain amount of time has passed without it being called.

Explanation: Use setTimeout and clearTimeout. Each time the debounced function is called, clear the previous timeout and set a new one. The actual function call happens only when the timeout completes.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Example usage: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Called after 1 sec (if not called again) debouncedHello(); // Resets timer debouncedHello(); // Resets timer, will actually run 1 sec after this call.

Throttle Function

Implement a throttle function. Throttling ensures that a function is called at most once in a specified time interval.

Explanation: Use a flag to indicate if a call is allowed. When called, if allowed, execute the function, set the flag to false, and use setTimeout to reset the flag after the interval.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Example usage: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Will log at most once per second.

Currying Function

Write a function that takes a function with two arguments and returns a curried version of it.

Explanation: Currying transforms a function with multiple arguments into a sequence of functions, each taking a single argument. f(a, b) becomes 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

Deep Clone an Object

Write a function to perform a deep clone of a JavaScript object.

Explanation: A shallow clone copies only the top-level properties. A deep clone recursively copies all properties, including nested objects and arrays. A simple way (with limitations, e.g., doesn't handle functions, dates, regex well) is using JSON.parse(JSON.stringify(obj)). A recursive approach is more robust.

function deepClone(obj) { // Simple version (with limitations) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Cloning failed:", e); return null; } // More robust recursive (basic example): /* 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 (proves it's a deep clone) console.log(cloned.b.c); // 3

Get Object Keys

How to get an array of keys from an object?

Explanation: Use Object.keys(obj).

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

Get Object Values

How to get an array of values from an object?

Explanation: Use Object.values(obj).

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

Check if Array Includes Value

How to check if an array contains a specific value?

Explanation: Use Array.prototype.includes(value).

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

Merge Two Arrays

How to merge two arrays into one?

Explanation: Use the spread syntax (...) or Array.prototype.concat().

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

Explain 'this' in Global Scope

What does this refer to in the global scope in a browser?

Explanation: In the global execution context (outside any function), this refers to the global object, which is window in web browsers.

console.log(this === window); // true (in a browser environment)

Explain 'this' in an Object Method

What does this refer to when used inside an object method?

Explanation: When a function is called as a method of an object, this refers to the object the method is called on.

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

Explain 'this' with Arrow Functions

How do arrow functions handle this?

Explanation: Arrow functions do not have their own this context. Instead, they inherit this from the surrounding (lexical) scope where they are defined.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Logs 42 because 'this' is inherited }, 100); } new MyClass();

Difference between `let`, `const`, and `var`

What are the main differences between let, const, and var?

Explanation: var is function-scoped and hoisted. let and const are block-scoped and hoisted but are in a 'temporal dead zone' until declaration. const must be initialized and cannot be reassigned.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Re-declares and affects outer 'a' let b = 20; // New 'b' within block // const c = 30; // Would be a new 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

What is a Promise?

Explain what a Promise is in JavaScript.

Explanation: A Promise is an object representing the eventual completion (or failure) of an asynchronous operation and its resulting value. It can be in one of three states: pending, fulfilled, or rejected.

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

Using `async/await`

How do async and await simplify working with Promises?

Explanation: async/await provides syntactic sugar over Promises, making asynchronous code look and behave a bit more like synchronous code. An async function always returns a Promise, and await pauses execution until a Promise settles.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Starting...'); await delay(1000); console.log('Waited 1 second.'); await delay(500); console.log('Waited another 0.5 seconds.'); return 'Finished!'; } run().then(console.log);

Convert String to Number

How to convert a string to a number?

Explanation: Use parseInt(), parseFloat(), or the unary plus operator (+).

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

Convert Number to String

How to convert a number to a string?

Explanation: Use String(), number.toString(), or string concatenation.

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

What is `JSON.stringify`?

What does JSON.stringify do?

Explanation: It converts a JavaScript object or value to a JSON string.

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

What is `JSON.parse`?

What does JSON.parse do?

Explanation: It parses a JSON string, constructing the JavaScript value or object described by the string.

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

Spread Operator in Arrays

How is the spread operator used with arrays?

Explanation: It allows an iterable (like an array) to be expanded in places where zero or more arguments or elements are expected. Useful for copying, merging, and adding elements.

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

Spread Operator in Objects

How is the spread operator used with objects?

Explanation: It allows copying and merging object properties.

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 }

Destructuring Arrays

Explain array destructuring with an example.

Explanation: It's a syntax that makes it possible to unpack values from arrays into distinct variables.

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

Destructuring Objects

Explain object destructuring with an example.

Explanation: It makes it possible to unpack properties from objects into distinct variables.

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'

Implement `call`

How could you implement a basic version of Function.prototype.call?

Explanation: call invokes a function with a specified this value and arguments provided individually. You can attach the function to the this context, call it, and then remove it.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Use a unique key 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!

Implement `apply`

How could you implement a basic version of Function.prototype.apply?

Explanation: apply is similar to call, but it accepts arguments as an 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.

Spiega l'Event Loop

Spiega brevemente l'Event Loop di JavaScript.

Spiegazione: JavaScript è a singolo thread, ma raggiunge la concorrenza usando un event loop. Lo stack di chiamate gestisce il codice sincrono. Le API web gestiscono operazioni asincrone (come setTimeout, fetch). Quando un'operazione asincrona termina, la sua callback va alla coda delle callback (o microtask queue per le Promise). L'event loop controlla costantemente se lo stack di chiamate è vuoto; se lo è, sposta la callback successiva dalla coda allo stack per l'esecuzione.

console.log('Start'); setTimeout(() => { console.log('Timeout Callback'); // Goes to Callback Queue }, 0); Promise.resolve().then(() => { console.log('Promise Resolved'); // Goes to Microtask Queue }); console.log('End'); // Output Order: Start, End, Promise Resolved, Timeout Callback // (Microtasks run before Macrotasks/Callbacks)

Ricerca Binaria

Implementa una funzione di ricerca binaria per un array ordinato.

Spiegazione: La ricerca binaria trova efficientemente un elemento in un array ordinato dividendo ripetutamente l'intervallo di ricerca a metà. Se il valore della chiave di ricerca è inferiore all'elemento al centro dell'intervallo, si restringe l'intervallo alla metà inferiore. Altrimenti, lo si restringe alla metà superiore. Si esegue questa operazione finché il valore non viene trovato o l'intervallo è vuoto.

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; // Found } else if (sortedArray[middle] < key) { start = middle + 1; // Search right half } else { end = middle - 1; // Search left half } } return -1; // Not found } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort

Implementa l'algoritmo Merge Sort.

Spiegazione: Merge Sort è un algoritmo divide-et-impera. Divide l'array di input in due metà, si richiama su entrambe le metà, quindi unisce le due metà ordinate. La funzione di unione è cruciale; combina due sottoarray ordinati in un unico array ordinato.

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

Implementa l'algoritmo Quick Sort.

Spiegazione: Quick Sort è anch'esso un algoritmo divide-et-impera. Sceglie un elemento come pivot e partiziona l'array dato attorno al pivot scelto. Gli elementi più piccoli del pivot vanno a sinistra, e gli elementi più grandi vanno a destra. Quindi ordina ricorsivamente i sottoarray.

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

Implementa l'algoritmo Bubble Sort.

Spiegazione: Bubble Sort è un semplice algoritmo di ordinamento che scorre ripetutamente la lista, confronta gli elementi adiacenti e li scambia se sono nell'ordine sbagliato. Il passaggio attraverso la lista viene ripetuto finché la lista non è ordinata.

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]]; // Swap swapped = true; } } n--; // Optimization: last element is already in place } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Sottostringa più lunga senza caratteri ripetuti

Data una stringa, trova la lunghezza della sottostringa più lunga senza caratteri ripetuti.

Spiegazione: Utilizza la tecnica della 'finestra scorrevole'. Mantieni una finestra (sottostringa) e un set di caratteri in quella finestra. Espandi la finestra spostando il puntatore destro. Se viene trovato un carattere ripetuto, restringi la finestra da sinistra finché il carattere ripetuto non viene rimosso.

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')

Implementa una Lista Collegata

Implementa una Lista Collegata Singolarmente con i metodi add e print.

Spiegazione: Una lista collegata è una struttura dati lineare dove gli elementi non sono memorizzati in posizioni di memoria contigue. Ogni elemento (nodo) punta al successivo. Hai bisogno di una classe Node e di una classe LinkedList per gestire la testa e l'aggiunta di nuovi nodi.

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

Implementa un Albero di Ricerca Binario (BST)

Implementa un Albero di Ricerca Binario con i metodi insert e find.

Spiegazione: Un BST è una struttura dati ad albero binario basata su nodi che ha le seguenti proprietà: il sottoalbero sinistro di un nodo contiene solo nodi con chiavi minori della chiave del nodo. Il sottoalbero destro di un nodo contiene solo nodi con chiavi maggiori della chiave del nodo. Entrambi i sottoalberi devono essere anche alberi di ricerca binari.

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

Ruota un Array

Scrivi una funzione per ruotare un array a destra di k passi.

Spiegazione: Ruotare un array significa spostare i suoi elementi. Per una rotazione a destra, l'ultimo elemento diventa il primo. Ripetere questo k volte funziona ma è inefficiente. Un modo migliore è usare la manipolazione dell'array o calcolare le nuove posizioni.

function rotateArray(nums, k) { k = k % nums.length; // Handle cases where 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]

Trova l'intersezione di Due Array

Dati due array, scrivi una funzione per calcolare la loro intersezione (elementi comuni ad entrambi).

Spiegazione: Un buon approccio è convertire un array in un Set per ricerche in tempo medio O(1). Quindi, iterare attraverso il secondo array e controllare se ogni elemento esiste nel set. Raccogli le corrispondenze.

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]

Raggruppa Anagrammi

Dato un array di stringhe, raggruppa gli anagrammi insieme.

Spiegazione: La chiave è trovare una firma unica per ogni gruppo di anagrammi. Un modo comune è ordinare ogni parola alfabeticamente. Le parole che risultano nella stessa stringa ordinata sono anagrammi. Usa una hash map dove le chiavi sono parole ordinate e i valori sono array di parole originali.

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'])); // Output: [['eat', 'tea', 'ate'], ['tan', 'nat'], ['bat']]

Sposta Zeri alla Fine

Dato un array nums, scrivi una funzione per spostare tutti gli 0 alla fine mantenendo l'ordine relativo degli elementi non zero.

Spiegazione: Utilizza un approccio 'a due puntatori' o 'a palla di neve'. Un puntatore tiene traccia della posizione in cui dovrebbe andare il prossimo elemento non zero. Itera attraverso l'array; se un elemento non è zero, posizionalo nella posizione del puntatore e incrementa il puntatore. Infine, riempi il resto con zeri.

function moveZeroes(nums) { let nonZeroIndex = 0; // Move all non-zero elements to the front for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Fill the rest with 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]

Sottoarray Massimo (Algoritmo di Kadane)

Dato un array di interi nums, trova il sottoarray contiguo (contenente almeno un numero) che ha la somma maggiore e restituisci la sua somma.

Spiegazione: L'algoritmo di Kadane è un modo efficiente per risolvere questo problema. Itera attraverso l'array, tenendo traccia della somma currentMax che termina nella posizione corrente e della somma globalMax trovata finora. Se currentMax diventa negativo, resettalo a 0 (o piuttosto, all'elemento corrente).

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 (from [4, -1, 2, 1])

Permutazioni di una Stringa

Scrivi una funzione per generare tutte le permutazioni di una data stringa.

Spiegazione: Questo è tipicamente risolto usando la ricorsione e il backtracking. Per ogni carattere, fissalo e genera ricorsivamente le permutazioni per il resto della stringa. Caso base: quando la stringa ha un solo carattere, restituiscilo.

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)]; // Use Set to handle duplicate chars if needed } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Massimo Comune Divisore (MCD)

Scrivi una funzione per trovare il Massimo Comune Divisore (MCD) di due numeri.

Spiegazione: L'algoritmo euclideo è un metodo efficiente. Se b è 0, a è il MCD. Altrimenti, il MCD di a e b è lo stesso del MCD di b e a % b (il resto di a diviso per 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

Minimo Comune Multiplo (MCM)

Scrivi una funzione per trovare il Minimo Comune Multiplo (MCM) di due numeri.

Spiegazione: Il MCM può essere calcolato usando il MCD con la formula: MCM(a, b) = |a * b| / MCD(a, b).

function gcd(a, b) { // Helper from previous problem 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

Implementa Promise.all

Implementa una funzione che si comporta come Promise.all.

Spiegazione: Promise.all prende un array di promise e restituisce una singola promise che si risolve quando tutte le promise di input si sono risolte, o si rifiuta se una qualsiasi promise di input si rifiuta. Il valore risolto è un array dei valori risolti.

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); // Reject immediately on any error }); }); } // Example usage: 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']

Inverti Albero Binario

Scrivi una funzione per invertire un albero binario.

Spiegazione: Per invertire un albero binario, si scambiano i figli sinistro e destro per ogni nodo. Questo può essere fatto ricorsivamente o iterativamente (usando una coda o uno stack).

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; } // Swap the children [root.left, root.right] = [root.right, root.left]; // Recur for left and right children invertTree(root.left); invertTree(root.right); return root; } // Example: 4 -> [2, 7] -> [1, 3, 6, 9] // Becomes: 4 -> [7, 2] -> [9, 6, 3, 1]

Profondità Massima dell'Albero Binario

Dato un albero binario, trova la sua profondità massima.

Spiegazione: La profondità massima è il numero di nodi lungo il percorso più lungo dal nodo radice al nodo foglia più lontano. Questo può essere trovato ricorsivamente: MaxDepth = 1 + max(MaxDepth(sinistro), MaxDepth(destro)). Il caso base è quando un nodo è nullo, la sua profondità è 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; } // Example: 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

Miglior Momento per Comprare/Vendere Azioni

Ti viene dato un array prices dove prices[i] è il prezzo di una data azione nel giorno i. Vuoi massimizzare il tuo profitto scegliendo un singolo giorno per comprare un'azione e scegliendo un giorno diverso in futuro per vendere quell'azione. Restituisci il profitto massimo.

Spiegazione: Itera attraverso i prezzi, tenendo traccia del prezzo minimo incontrato finora (minPrice) e del profitto massimo trovato finora (maxProfit). Per ogni giorno, calcola il potenziale profitto se vendessi oggi (prezzo corrente - minPrice) e aggiorna maxProfit se è superiore.

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 (Buy at 1, Sell at 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (No profit possible)

Numero Singolo

Dato un array non vuoto di interi nums, ogni elemento appare due volte tranne uno. Trova quell'unico.

Spiegazione: Un modo molto efficiente per risolvere questo problema è usare l'operatore bitwise XOR (^). Fare l'XOR di un numero con se stesso dà come risultato 0. Fare l'XOR di un numero con 0 dà come risultato il numero stesso. Se fai l'XOR di tutti i numeri nell'array, le coppie si annulleranno (diventeranno 0), lasciando solo il numero singolo.

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 Maggioritario

Dato un array nums di dimensione n, restituisci l'elemento maggioritario. L'elemento maggioritario è l'elemento che appare più di ⌊n / 2⌋ volte.

Spiegazione: L'algoritmo di voto di Boyer-Moore è un modo efficiente. Inizializza un candidate e un count. Itera attraverso l'array. Se count è 0, imposta l'elemento corrente come candidate. Se l'elemento corrente corrisponde a candidate, incrementa count; altrimenti, decrementa 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

Salire le Scale

Stai salendo una scala. Ci vogliono n gradini per raggiungere la cima. Ogni volta puoi salire 1 o 2 gradini. In quanti modi distinti puoi salire in cima?

Spiegazione: Questo è un classico problema di programmazione dinamica, molto simile alla sequenza di Fibonacci. Il numero di modi per raggiungere il gradino n è la somma dei modi per raggiungere il gradino n-1 (facendo 1 gradino) e dei modi per raggiungere il gradino n-2 (facendo 2 gradini). 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

Prodotto dell'Array Eccetto Se Stesso

Dato un array di interi nums, restituisci un array answer tale che answer[i] sia uguale al prodotto di tutti gli elementi di nums eccetto nums[i]. Non usare l'operatore di divisione.

Spiegazione: Puoi risolvere questo problema calcolando i prodotti dei prefissi e i prodotti dei suffissi. Il risultato all'indice i è il prodotto di tutti gli elementi prima di i moltiplicato per il prodotto di tutti gli elementi dopo i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calculate prefix products let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calculate suffix products and multiply with prefix 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]

Numero di Isole

Data una griglia 2D di '1' (terra) e '0' (acqua), conta il numero di isole. Un'isola è circondata dall'acqua ed è formata collegando terre adiacenti orizzontalmente o verticalmente.

Spiegazione: Itera attraverso ogni cella della griglia. Se trovi un '1' (terra), incrementa il conteggio delle isole e avvia una Ricerca in Profondità (DFS) o una Ricerca in Ampiezza (BFS) da quella cella. Durante la ricerca, segna tutti gli '1' collegati come '0' (o visitati) per evitare di contarli di nuovo.

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'; // Mark as visited 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 (Need to run on a copy or restore grid)

Cache LRU

Progetta e implementa una struttura dati per una cache Least Recently Used (LRU). Dovrebbe supportare le operazioni get e put.

Spiegazione: Una cache LRU espelle l'elemento meno recentemente usato quando la capacità è raggiunta. Un'implementazione comune utilizza una Map (o una hash map) per ricerche O(1) in tempo medio e una lista doppiamente collegata per aggiornamenti O(1) dello stato 'recentemente usato'. La Map di JavaScript mantiene l'ordine di inserimento, il che può semplificare un'implementazione di base.

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); // Remove to re-insert at the 'end' (most recent) 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); // Evicts 2 console.log(cache.get(2)); // -1

Genera Parentesi

Dato n coppie di parentesi, scrivi una funzione per generare tutte le combinazioni di parentesi ben formate.

Spiegazione: Questo è un classico problema di backtracking. Mantieni i conteggi per le parentesi aperte e chiuse. Puoi aggiungere una parentesi aperta se open < n. Puoi aggiungere una parentesi chiusa se close < open. Il caso base è quando la lunghezza della stringa raggiunge 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)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Contenitore con Più Acqua

Dati n interi non negativi a1, a2, ..., an, dove ciascuno rappresenta un punto con coordinata (i, ai). n linee verticali sono disegnate in modo tale che i due estremi della linea i siano in (i, ai) e (i, 0). Trova due linee che, insieme all'asse x, formano un contenitore, tale che il contenitore contenga la maggior quantità di acqua.

Spiegazione: Utilizza l'approccio dei due puntatori. Inizia con un puntatore all'inizio e uno alla fine. Calcola l'area. L'area è limitata dalla linea più corta. Per trovare potenzialmente un'area più grande, sposta il puntatore che punta alla linea più corta verso l'interno.

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

Dato un array nums di n interi, ci sono elementi a, b, c in nums tali che a + b + c = 0? Trova tutte le triplette uniche nell'array che danno come somma zero.

Spiegazione: Per prima cosa, ordina l'array. Poi, itera attraverso l'array con un puntatore i. Per ogni i, usa altri due puntatori, left (che inizia da i+1) e right (che inizia dalla fine), per trovare due numeri la cui somma sia -nums[i]. Gestisci i duplicati per assicurare triplette uniche.

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; // Skip duplicates for 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++; // Skip duplicates while (left < right && nums[right] === nums[right - 1]) right--; // Skip duplicates 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]]

Cerca Posizione di Inserimento

Dato un array ordinato di interi distinti e un valore target, restituisci l'indice se il target viene trovato. Se non lo è, restituisci l'indice in cui sarebbe se fosse inserito in ordine.

Spiegazione: Questa è una variazione della ricerca binaria. Esegui la ricerca binaria, ma se l'elemento non viene trovato, il puntatore start si troverà nella posizione in cui l'elemento dovrebbe essere inserito.

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; // If not found, start is the insertion point } 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

Unisci Due Liste Ordinate (Liste Collegata)

Unisci due liste collegate ordinate e restituiscile come una nuova lista ordinata. La nuova lista dovrebbe essere creata unendo i nodi delle prime due liste.

Spiegazione: Utilizza un nodo fittizio (dummy head) per semplificare il processo. Usa un puntatore per costruire la nuova lista. Confronta i nodi correnti di entrambe le liste di input e aggiungi il più piccolo alla nuova lista, avanzando il puntatore di quella lista. Ripeti finché una lista non è esaurita, quindi aggiungi il resto dell'altra 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; } // Example: 1->2->4 and 1->3->4 => 1->1->2->3->4->4

Albero Simmetrico

Dato un albero binario, controlla se è uno specchio di se stesso (cioè, simmetrico rispetto al suo centro).

Spiegazione: Questo può essere risolto ricorsivamente. Un albero è simmetrico se il sottoalbero sinistro è un'immagine speculare del sottoalbero destro. Crea una funzione di supporto isMirror(t1, t2) che verifica se due alberi sono speculari. Dovrebbe verificare: 1. t1.val === t2.val. 2. t1.left è uno specchio di t2.right. 3. t1.right è uno specchio di 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); } // Example: 1 -> [2, 2] -> [3, 4, 4, 3] is symmetric.

Attraversamento per Livelli (BST/Albero)

Dato un albero binario, restituisci l'attraversamento per livelli dei valori dei suoi nodi. (cioè, da sinistra a destra, livello per livello).

Spiegazione: Questa è una ricerca in ampiezza (BFS). Usa una coda. Inizia aggiungendo la radice alla coda. Finché la coda non è vuota, elabora tutti i nodi al livello corrente. Per ogni nodo elaborato, aggiungi i suoi figli (se esistono) alla coda per il livello successivo.

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; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] // Output: [[3], [9, 20], [15, 7]]

Converti Array Ordinato in BST Bilanciato in Altezza

Dato un array i cui elementi sono ordinati in ordine crescente, convertilo in un albero di ricerca binario (BST) bilanciato in altezza.

Spiegazione: Per creare un BST bilanciato in altezza, dovresti scegliere l'elemento centrale dell'array come radice. Quindi, costruisci ricorsivamente il sottoalbero sinistro dalla metà sinistra dell'array e il sottoalbero destro dalla metà destra.

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); } // Example: [-10, -3, 0, 5, 9] // Output: A tree like [0, -3, 9, -10, null, 5, null]

Implementa `bind`

Implementa la tua versione di Function.prototype.bind.

Spiegazione: bind crea una nuova funzione che, quando chiamata, ha la sua parola chiave this impostata al valore fornito, con una data sequenza di argomenti che precedono qualsiasi argomento fornito quando la nuova funzione viene chiamata. Usa apply o call all'interno di una funzione restituita, gestendo l'applicazione parziale (argomenti preimpostati).

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 is global/window) const boundGetX = unboundGetX.myBind(module); console.log(boundGetX()); // 42

Trova il K-esimo Elemento Più Grande

Trova il k-esimo elemento più grande in un array non ordinato. Nota che è il k-esimo elemento più grande nell'ordine ordinato, non il k-esimo elemento distinto.

Spiegazione: Un approccio semplice è ordinare l'array e quindi scegliere l'elemento all'indice n - k. Un approccio più efficiente per array grandi implica l'uso di un min-heap o di un algoritmo di selezione come Quickselect.

function findKthLargest(nums, k) { // Simple approach: Sort nums.sort((a, b) => b - a); // Sort in descending order return nums[k - 1]; // Note: Quickselect would be more efficient in an interview // but sorting is easier to implement quickly. } 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

Controlla se l'Oggetto ha una Proprietà

Come puoi controllare se un oggetto ha una proprietà specifica?

Spiegazione: Puoi usare l'operatore in (controlla le proprietà proprie ed ereditate), Object.prototype.hasOwnProperty.call(obj, prop) (controlla solo le proprietà proprie), o semplicemente obj.prop !== undefined (può essere complicato con valori undefined).

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

Intero a Romano

Scrivi una funzione per convertire un intero nella sua rappresentazione in numeri romani.

Spiegazione: Crea una mappatura dei numeri romani e dei loro valori corrispondenti, ordinati dal più grande al più piccolo. Itera su questa mappa. Per ogni valore, sottrailo dal numero di input il maggior numero di volte possibile, aggiungendo il numero romano ogni volta.

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 a Intero

Scrivi una funzione per convertire un numero romano in un intero.

Spiegazione: Crea una mappa dei simboli romani ai valori. Itera attraverso la stringa. Se il valore del simbolo corrente è inferiore al valore del simbolo successivo (come 'IV' o 'IX'), sottrai il valore corrente; altrimenti, aggiungilo.

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

Implementa un `Set`

Implementa una struttura dati Set di base (senza usare il Set predefinito) con i metodi add, has, delete e size.

Spiegazione: Puoi usare un oggetto JavaScript (hash map) come archivio sottostante. Le chiavi saranno gli elementi del set (potrebbe essere necessario gestire come memorizzare tipi diversi e garantire l'unicità come chiavi).

class MySet { constructor() { this.items = {}; this._size = 0; } add(element) { if (!this.has(element)) { this.items[element] = element; // Store the element 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

Triangolo di Pascal

Dato un intero numRows, genera le prime numRows del triangolo di Pascal.

Spiegazione: Nel triangolo di Pascal, ogni numero è la somma dei due numeri direttamente sopra di esso. Inizia con [[1]]. Per ogni riga successiva, inizia e finisci con 1. Ogni elemento centrale è la somma dell'elemento allo stesso indice e dell'indice precedente dalla riga superiore.

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]]

Ricerca di Parole

Data una board 2D e una parola, trova se la parola esiste nella griglia. La parola può essere costruita da lettere di celle adiacenti sequenzialmente, dove le celle 'adiacenti' sono vicine orizzontalmente o verticalmente.

Spiegazione: Questo richiede una ricerca in profondità (DFS) con backtracking. Itera attraverso ogni cella. Se una cella corrisponde alla prima lettera della parola, avvia una DFS. La funzione DFS controlla i vicini, assicurandosi che corrispondano alla lettera successiva e che non siano stati visitati nel percorso corrente. Se un percorso fallisce, torna indietro deselezionando la cella.

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // Word found if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Mark as visited 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; // Backtrack 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

Sottostringa Finestra Minima

Date due stringhe s e t, trova la finestra minima in s che conterrà tutti i caratteri in t. Se non esiste una tale finestra in s che copra tutti i caratteri in t, restituisci la stringa vuota "".

Spiegazione: Utilizza un approccio a finestra scorrevole con due puntatori (left e right) e hash map. Una mappa memorizza i conteggi dei caratteri necessari da t. Un'altra mappa memorizza i conteggi nella finestra corrente. Espandi la finestra con right. Una volta che la finestra contiene tutti i caratteri necessari, prova a restringila con left per trovare la finestra minima possibile.

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'

Inverti Lista Collegata

Data la head, la testa di una lista collegata singolarmente, inverti la lista e restituisci la lista invertita.

Spiegazione: Devi iterare attraverso la lista, cambiando il puntatore next di ogni nodo in modo che punti al nodo precedente. Tieni traccia dei nodi previous, current e next durante l'iterazione.

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; // Store next node current.next = prev; // Reverse current node's pointer prev = current; // Move prev one step forward current = next; // Move current one step forward } return prev; // New head is the last 'prev' } // Example: 1->2->3->4->5 becomes 5->4->3->2->1

Rileva Ciclo nella Lista Collegata

Data la head, la testa di una lista collegata, determina se la lista collegata ha un ciclo.

Spiegazione: Usa l'algoritmo 'Floyd's Tortoise and Hare'. Avere due puntatori, uno che si muove di un passo alla volta (slow) e uno che si muove di due passi alla volta (fast). Se c'è un ciclo, il puntatore fast alla fine raggiungerà il puntatore 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; // Reached end, no cycle slow = slow.next; fast = fast.next.next; } return true; // Pointers met, cycle exists } // Example: 3->2->0->-4 with -4 pointing back to 2. hasCycle returns true.

Implementa `Object.create`

Implementa una funzione che imiti il comportamento di Object.create(proto).

Spiegazione: Object.create crea un nuovo oggetto, usando un oggetto esistente come prototipo dell'oggetto appena creato. Puoi ottenere questo creando una funzione costruttore temporanea, impostando il suo prototipo sull'input proto e quindi restituendo una nuova istanza di esso.

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

Cos'è l'Hoisting?

Spiega l'hoisting in JavaScript e fornisci un esempio.

Spiegazione: L'hoisting è il comportamento predefinito di JavaScript di spostare le dichiarazioni all'inizio dell'ambito corrente (globale o funzione) prima dell'esecuzione del codice. Le dichiarazioni di variabile (var) vengono sollevate e inizializzate con undefined. Le dichiarazioni di funzione sono completamente sollevate (sia il nome che il corpo). let e const sono sollevate ma non inizializzate, portando a una Temporal Dead Zone.

console.log(myVar); // undefined (var is hoisted and initialized with undefined) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (Function declaration is fully hoisted) var myVar = 'I am a var'; let myLet = 'I am a let'; function myFunc() { console.log('Hello!'); }

Spiega Event Bubbling e Capturing

Spiega Event Bubbling e Capturing nel contesto del DOM.

Spiegazione: Queste sono due fasi della propagazione degli eventi nel DOM HTML. Fase di cattura: L'evento si sposta dalla window all'elemento target. Fase di bubbling: L'evento si sposta dall'elemento target di nuovo alla window. Per impostazione predefinita, la maggior parte dei gestori di eventi sono registrati durante la fase di bubbling. Puoi usare addEventListener(type, listener, useCapture) con useCapture = true per gestire gli eventi durante la fase di cattura.

// In an HTML structure: <div><p><span>Click Me</span></p></div> // If you click the <span>: // Capturing: window -> document -> html -> body -> div -> p -> span // Bubbling: span -> p -> div -> body -> html -> document -> window /* JS Example div.addEventListener('click', () => console.log('Div clicked'), true); // Capturing p.addEventListener('click', () => console.log('P clicked'), false); // Bubbling span.addEventListener('click', () => console.log('Span clicked'), false); // Bubbling // Output would be: Div clicked, Span clicked, P clicked */

Implementa `JSON.parse` manualmente (base)

Prova a implementare una versione molto basilare di JSON.parse (gestisce oggetti semplici, array, stringhe, numeri).

Spiegazione: Questo è un compito molto complesso nella sua interezza, ma per un'impostazione di codifica dal vivo, potresti essere chiesto di gestire un sottoinsieme molto semplificato. Dovresti analizzare la stringa, identificando i limiti di oggetto {} e array [], le coppie chiave-valore (key: value), e i tipi di dati di base. eval o new Function possono barare ma sono pericolosi. Un vero parser userebbe un lexer/tokenizer e un parser.

function basicJsonParse(jsonString) { // WARNING: Using new Function is insecure like eval. // This is a simplified, INSECURE example for demonstration only. // A real implementation requires a proper parser. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('Invalid JSON: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

Appiattisci un Array Profondamente Annidato

Scrivi una funzione per appiattire un array profondamente annidato.

Spiegazione: A differenza dell'appiattimento precedente (un livello), questo deve gestire qualsiasi livello di annidamento. La ricorsione è una soluzione naturale. Itera attraverso l'array. Se un elemento è un array, chiama ricorsivamente flatten su di esso e concatena il risultato. Altrimenti, aggiungi l'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+ provides a much simpler way: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Implementa un Trie (Albero dei Prefissi)

Implementa una struttura dati Trie con i metodi insert, search e startsWith.

Spiegazione: Un Trie è una struttura dati ad albero utilizzata per memorizzare e recuperare in modo efficiente le chiavi in un set di dati di stringhe. Ogni nodo rappresenta un carattere e i percorsi dalla radice rappresentano parole o prefissi.

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

Mischia un Array (Fisher-Yates)

Scrivi una funzione per mescolare un array sul posto usando l'algoritmo Fisher-Yates (o Knuth).

Spiegazione: L'algoritmo Fisher-Yates itera un array dall'ultimo elemento al primo. In ogni iterazione, scambia l'elemento corrente con un elemento scelto casualmente dall'inizio dell'array fino all'elemento corrente (incluso).

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]]; // Swap elements } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // e.g., [3, 5, 1, 2, 4]

Componi Funzioni

Implementa una funzione compose che prende più funzioni e restituisce una nuova funzione che le applica da destra a sinistra.

Spiegazione: La composizione di funzioni (f ∘ g)(x) = f(g(x)) applica una funzione al risultato di un'altra. Una compose(f, g, h) generale significherebbe f(g(h(x))). Usa reduceRight per un'implementazione 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

Pipeline di Funzioni

Implementa una funzione pipe che prende più funzioni e restituisce una nuova funzione che le applica da sinistra a destra.

Spiegazione: Simile a compose, ma l'ordine di applicazione è invertito: pipe(f, g, h) significa h(g(f(x))). Usa reduce per l'implementazione.

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 del Cambio Monete

Dato un array di denominazioni di monete e un importo, trova il numero minimo di monete per raggiungere quell'importo. Supponi una scorta infinita di ogni moneta.

Spiegazione: Questo è un classico problema di programmazione dinamica. Crea un array dp dove dp[i] memorizza il numero minimo di monete necessarie per l'importo i. dp[i] = min(dp[i - coin]) + 1 per tutte le monete.

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

Antenato Comune Più Basso (BST)

Dato un albero di ricerca binario (BST), trova l'antenato comune più basso (LCA) di due nodi dati nel BST.

Spiegazione: L'LCA è il nodo più profondo che ha entrambi i nodi dati come discendenti. In un BST, puoi trovarlo attraversando dalla radice. Se entrambi i nodi sono più piccoli del nodo corrente, vai a sinistra. Se entrambi sono più grandi, vai a destra. Se uno è più piccolo e uno è più grande (o uno è il nodo corrente), allora il nodo corrente è l'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; // Found LCA } } return null; // Should not happen in a valid BST with p and q } // Example: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA is 6

Serializza e Deserializza Albero Binario

Progetta un algoritmo per serializzare e deserializzare un albero binario.

Spiegazione: La serializzazione converte un albero in una stringa o array. La deserializzazione ricostruisce l'albero. Un modo comune è l'attraversamento in pre-ordine (DFS). Usa un marcatore speciale (ad esempio, '#') per i nodi nulli per preservare la struttura.

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(); } // Example: 1 -> [2, 3] -> [null, null, 4, 5] // Serialized: '1,2,#,#,3,4,#,#,5,#,#'

Implementa `setInterval` con `setTimeout`

Implementa una funzione mySetInterval che imita setInterval ma usa setTimeout ricorsivamente.

Spiegazione: setInterval esegue una funzione ripetutamente ogni N millisecondi. Puoi ottenere questo facendo in modo che una funzione chiami se stessa con setTimeout dopo ogni esecuzione. Devi anche avere un modo per cancellarla (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // Schedule the next call }, delay); } run(); return interval; // Return an object to allow clearing } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Example usage: let count = 0; const intervalId = mySetInterval(() => { console.log(`Hello: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

Attraversamento del Grafo (BFS & DFS)

Implementa la ricerca in ampiezza (BFS) e la ricerca in profondità (DFS) per un grafo dato (rappresentazione a lista di adiacenza).

Spiegazione: BFS esplora prima i vicini (usando una Coda), mentre DFS esplora il più lontano possibile lungo ogni ramo (usando uno Stack o la ricorsione). Tieni traccia dei nodi visitati per evitare cicli e lavoro ridondante.

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); // Add neighbors in reverse to process them in order later (optional) 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']

Ruota Immagine (Matrice)

Ti viene data una matrice 2D n x n che rappresenta un'immagine. Ruota l'immagine di 90 gradi (in senso orario) sul posto.

Spiegazione: Un modo comune per ottenere questo è prima trasponendo la matrice (scambiando matrix[i][j] con matrix[j][i]) e quindi invertendo ogni riga.

function rotate(matrix) { const n = matrix.length; // Transpose 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]]; } } // Reverse each row 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]]

Attraversamento della Matrice a Spirale

Data una matrice m x n, restituisce tutti gli elementi della matrice in ordine a spirale.

Spiegazione: Usa quattro puntatori per definire i confini: top, bottom, left, right. Attraversa la riga superiore, poi la colonna destra, poi la riga inferiore, poi la colonna sinistra, riducendo i confini dopo ogni attraversamento, finché left non incrocia right o top non incrocia 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]

Imposta Zeri nella Matrice

Data una matrice m x n, se un elemento è 0, imposta l'intera sua riga e colonna a 0. Fallo in-place.

Spiegazione: Un approccio ingenuo richiede uno spazio extra di O(m*n). Per farlo in spazio O(1) (o O(m+n)), puoi usare la prima riga e la prima colonna per memorizzare le informazioni su quali righe/colonne devono essere azzerate. Hai bisogno di flag separati per indicare se la prima riga/colonna stessa deve essere azzerata.

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; } // Example: [[1,1,1],[1,0,1],[1,1,1]] => [[1,0,1],[0,0,0],[1,0,1]]

Implementa Promise.race

Implementa una funzione che si comporta come Promise.race.

Spiegazione: Promise.race accetta un array di promise e restituisce una singola promise. Questa promise restituita si risolve (o si rifiuta) non appena una qualsiasi delle promise di input si risolve, con il valore o la ragione di quella promise.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // Or resolve/reject depending on spec } 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'

Pattern Singleton

Implementa il pattern di progettazione Singleton in JavaScript.

Spiegazione: Il pattern Singleton garantisce che una classe abbia una sola istanza e fornisce un punto di accesso globale ad essa. Ciò può essere ottenuto utilizzando una closure per contenere l'istanza.

const Singleton = (function() { let instance; function createInstance() { // Private methods and variables const privateVar = 'I am private'; function privateMethod() { console.log('Private'); } return { // Public methods and variables publicVar: 'I am public', publicMethod: function() { console.log('Public'); }, 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()); // 'I am private'

Valida Indirizzo IP

Scrivi una funzione per verificare se una data stringa è un indirizzo IPv4 o IPv6 valido.

Spiegazione: Per IPv4, controlla che ci siano 4 numeri (0-255) separati da punti, senza zeri iniziali (eccetto '0' stesso). Per IPv6, controlla che ci siano 8 gruppi di 1-4 cifre esadecimali separate da due punti (gestisci le variazioni come '::'). Si possono usare le espressioni regolari, ma l'analisi manuale è spesso più chiara per i colloqui.

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; // Simplified: No '::' 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 (Simplified) console.log(validIPAddress('256.256.256.256')); // Neither

Trova Elemento Picco

Un elemento picco è un elemento strettamente maggiore dei suoi vicini. Data una matrice di input nums, trova un elemento picco e restituisci il suo indice.

Spiegazione: Poiché qualsiasi picco va bene, e nums[-1] e nums[n] sono considerati -Infinito, puoi usare una ricerca binaria modificata. Se nums[mid] è minore di nums[mid+1], un picco deve esistere a destra. Altrimenti, un picco deve esistere a sinistra (o mid stesso è un picco).

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; // Peak is to the right } else { right = mid; // Peak is mid or to the left } } return left; // 'left' will be the index of a peak } console.log(findPeakElement([1, 2, 3, 1])); // 2 (index of 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (index of 6) or 1 (index of 2)

Conteggio Bit

Dato un intero n, restituisci un array ans di lunghezza n + 1 tale che per ogni i (0 <= i <= n), ans[i] è il numero di 1 nella rappresentazione binaria di i.

Spiegazione: Puoi risolvere questo problema usando la programmazione dinamica. Nota la relazione: dp[i] = dp[i >> 1] + (i & 1). Il numero di 1 in i è il numero di 1 in i spostato a destra (cioè, i/2), più 1 se i è dispari.

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); // Or: dp[i] = dp[i & (i - 1)] + 1; (Removes last set bit) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

Potenza di Due

Dato un intero n, restituisci true se è una potenza di due. Altrimenti, restituisci false.

Spiegazione: Una potenza di due nella rappresentazione binaria ha esattamente un bit '1' (ad esempio, 1=1, 2=10, 4=100, 8=1000). Un trucco intelligente a livello di bit è controllare se n > 0 e (n & (n - 1)) === 0. Se n è una potenza di due, n-1 avrà tutti i bit sotto quel '1' impostati a '1'. L'operazione AND tra di essi produce 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

Unisci Intervalli

Dato un array di intervalli dove intervals[i] = [starti, endi], unisci tutti gli intervalli sovrapposti e restituisci un array degli intervalli non sovrapposti.

Spiegazione: Innanzitutto, ordina gli intervalli in base ai loro orari di inizio. Quindi, itera attraverso gli intervalli ordinati. Se l'intervallo corrente si sovrappone con l'ultimo intervallo nell'elenco dei risultati, uniscili aggiornando l'orario di fine dell'ultimo intervallo. Altrimenti, aggiungi l'intervallo corrente all'elenco dei risultati.

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]) { // Overlap: merge last[1] = Math.max(last[1], current[1]); } else { // No overlap: add new interval merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

Interruzione di Parola

Data una stringa s e un dizionario di stringhe wordDict, restituisci true se s può essere segmentata in una sequenza separata da spazi di una o più parole del dizionario.

Spiegazione: Usa la programmazione dinamica. Crea un array booleano dp dove dp[i] è true se la sottostringa s[0...i-1] può essere segmentata. dp[i] è true se esiste un j < i tale che dp[j] è true e la sottostringa s[j...i-1] è nel dizionario.

function wordBreak(s, wordDict) { const wordSet = new Set(wordDict); const n = s.length; const dp = new Array(n + 1).fill(false); dp[0] = true; // Base case: empty string 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

Implementa Array.prototype.flat

Implementa la tua versione di Array.prototype.flat, che crea un nuovo array con tutti gli elementi delle sotto-array concatenati ricorsivamente fino a una profondità specificata.

Spiegazione: Usa la ricorsione. Itera attraverso l'array. Se un elemento è un array e la profondità corrente è inferiore alla profondità specificata, chiama ricorsivamente flatten su di esso. Altrimenti, aggiungi l'elemento. Gestisci la profondità predefinita di 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]

Inverti Parole in una Stringa

Data una stringa di input s, inverti l'ordine delle parole.

Spiegazione: Una 'parola' è definita come una sequenza di caratteri non-spazio. Taglia la stringa, dividila per uno o più spazi, filtra le stringhe vuote risultanti da spazi multipli, inverti l'array e uniscilo di nuovo con singoli spazi.

function reverseWords(s) { return s.trim().split(/\s+/).reverse().join(' '); } console.log(reverseWords('the sky is blue')); // 'blue is sky the' console.log(reverseWords(' hello world ')); // 'world hello' console.log(reverseWords('a good example')); // 'example good a'

Parser di Stringa di Query

Scrivi una funzione per analizzare una stringa di query URL in un oggetto.

Spiegazione: Dividi la stringa per &. Per ogni parte, dividi per = per ottenere la chiave e il valore. Decodifica i componenti URI sia per la chiave che per il valore. Gestisci i casi in cui una chiave potrebbe apparire più volte (crea un array) o non ha valore.

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 }

Usa Proxy per la Validazione

Dimostra come usare un oggetto Proxy per la validazione delle proprietà degli oggetti.

Spiegazione: Un Proxy ti permette di intercettare e personalizzare le operazioni eseguite su un oggetto. Usa il trap set per validare i valori prima di assegnarli a una proprietà. Lancia un errore se la validazione fallisce.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value < 0 || value > 150) { throw new RangeError('The age seems invalid'); } } // Default behavior: Set the property obj[prop] = value; return true; } }; const person = {}; const personProxy = new Proxy(person, validator); personProxy.age = 30; // OK console.log(personProxy.age); // 30 // personProxy.age = 'thirty'; // Throws TypeError // personProxy.age = 200; // Throws RangeError

Sale Riunioni

Dato un array di intervalli di tempo di riunioni [[start1, end1], [start2, end2], ...], determina se una persona potrebbe partecipare a tutte le riunioni.

Spiegazione: Se una persona può partecipare a tutte le riunioni, significa che nessuna delle due riunioni si sovrappone. Per verificarlo, ordina gli intervalli in base ai loro orari di inizio. Quindi, scorri gli intervalli ordinati e controlla se l'orario di inizio della riunione corrente è precedente all'orario di fine della riunione precedente. In tal caso, c'è una sovrapposizione e la persona non può partecipare a tutte le riunioni.

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; // Overlap detected } } return true; } console.log(canAttendMeetings([[0, 30], [5, 10], [15, 20]])); // false (5<30) console.log(canAttendMeetings([[7, 10], [2, 4]])); // true

Implementa Promise.any

Implementa una funzione che si comporta come Promise.any.

Spiegazione: Promise.any accetta un array di promise e restituisce una singola promise. Questa promise si risolve non appena una qualsiasi delle promise di input si risolve. Se tutte le promise di input si rifiutano, si rifiuta con un AggregateError contenente tutte le ragioni di rifiuto.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'All promises were rejected')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Resolve as soon as one fulfills .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'All promises were rejected')); } }); }); }); } // Example: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Example: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

Pattern Osservatore

Implementa il pattern di progettazione Observer (Pub/Sub) in JavaScript.

Spiegazione: Il pattern Observer definisce una dipendenza uno-a-molti tra oggetti. Quando un oggetto (Subject) cambia stato, tutti i suoi dipendenti (Observers) vengono notificati e aggiornati automaticamente. Il Subject mantiene un elenco di osservatori e fornisce metodi per aggiungerli, rimuoverli e notificarli.

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} received: ${data}`); } } const subject = new Subject(); const obs1 = new Observer('Observer 1'); const obs2 = new Observer('Observer 2'); subject.subscribe(obs1); subject.subscribe(obs2); subject.notify('Something happened!'); // Observer 1 received: Something happened! // Observer 2 received: Something happened!

Trova Numero Duplicato

Dato un array di interi nums contenente n + 1 interi dove ogni intero è nell'intervallo [1, n] inclusivo, trova il numero ripetuto.

Spiegazione: Poiché i numeri sono in [1, n] e la dimensione dell'array è n+1, garantisce almeno un duplicato. Questo può essere mappato a un problema di 'Trova Ciclo in Lista Collegata' (Floyd's Tortoise and Hare). Tratta i valori dell'array come puntatori: index -> nums[index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Phase 1: Find the intersection point do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Phase 2: Find the entrance to the cycle 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

Sanitizzatore HTML Base

Scrivi una funzione base per sanitizzare una stringa HTML, rimuovendo tag potenzialmente dannosi (come script) mantenendo tag sicuri (come p, b).

Spiegazione: Questo è un argomento complesso e i sanitizzatori reali sono sofisticati. Per un colloquio, un approccio di base potrebbe comportare l'uso di espressioni regolari per rimuovere tag specifici o consentire solo una whitelist di tag. Questo non è sicuro per la produzione.

function basicSanitize(html) { // WARNING: This is a very basic and insecure example. // Do NOT use this in production. Use a library like DOMPurify. // Remove script tags let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Example: Remove onclick attributes (very basic) sanitized = sanitized.replace(/\s(on\w+)=['"]?[^'"]*['"]?/gi, ''); return sanitized; } console.log(basicSanitize('<p>Hello <script>alert("XSS")</script> <b onclick="danger()">World</b></p>')); // <p>Hello <b >World</b></p>

Distanza di Modifica

Date due stringhe word1 e word2, restituisci il numero minimo di operazioni (inserimento, eliminazione o sostituzione) necessarie per convertire word1 in word2.

Spiegazione: Questo è un classico problema di programmazione dinamica (distanza di Levenshtein). Crea un array 2D dp dove dp[i][j] è la distanza di modifica tra i primi i caratteri di word1 e i primi j caratteri di word2. La relazione di ricorrenza comporta la considerazione del costo di inserimento, eliminazione e sostituzione.

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, // Deletion dp[i][j - 1] + 1, // Insertion dp[i - 1][j - 1] + cost // Substitution/Match ); } } return dp[m][n]; } console.log(minDistance('horse', 'ros')); // 3 console.log(minDistance('intention', 'execution')); // 5

Sottosequenza Crescente Più Lunga (LIS)

Dato un array di interi nums, restituisci la lunghezza della sottosequenza strettamente crescente più lunga.

Spiegazione: Usa la programmazione dinamica. Sia dp[i] la lunghezza della LIS che termina all'indice i. Per calcolare dp[i], trova tutti i j < i tali che nums[j] < nums[i], e prendi dp[i] = 1 + max(dp[j]). La risposta finale è il valore massimo nell'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 delle N-Regine

Il puzzle delle N-Regine è il problema di posizionare N regine di scacchi su una scacchiera N×N in modo che nessuna delle due regine si minacci a vicenda. Dato N, restituisci una soluzione valida (o tutte).

Spiegazione: Usa il backtracking. Posiziona le regine riga per riga. Per ogni riga, prova a posizionare una regina in ogni colonna. Prima di posizionare, controlla se la posizione proposta è sicura (non attaccata da regine nelle righe precedenti). Se sicura, posizionala e ricorsivamente chiama per la riga successiva. Se la ricorsione fallisce, torna indietro (rimuovi la regina) e prova la colonna successiva.

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; // Check col for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Check diag up-left for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Check diag up-right 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] = '.'; // Backtrack } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

Usa WeakMap per Dati Privati

Dimostra come usare WeakMap per memorizzare dati privati per le istanze di classe.

Spiegazione: WeakMap ti permette di associare dati a un oggetto in un modo che non impedisce la garbage collection se l'oggetto non è più referenziato. Questo è utile per creare membri 'privati' nelle classi JavaScript prima che i campi privati nativi fossero ampiamente disponibili o per casi d'uso specifici.

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' isn't a public property // console.log(privateData.get(person)); // Accessible, but not directly via 'person.'

Implementa Promise.allSettled

Implementa una funzione che si comporta come Promise.allSettled.

Spiegazione: Promise.allSettled accetta un array di promise e restituisce una singola promise. Questa promise si risolve quando tutte le promise di input si sono stabilite (o risolte o rifiutate). Il valore di risoluzione è un array di oggetti, ognuno dei quali descrive l'esito di una promise ({status: 'fulfilled', value: ...} o {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); } }); }); }); } // Example: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

Trova la Mediana di Due Array Ordinati

Dati due array ordinati nums1 e nums2 di dimensione m e n rispettivamente, restituisci la mediana dei due array ordinati.

Spiegazione: Questo è un problema difficile spesso risolto con un approccio di ricerca binaria sull'array più piccolo. L'obiettivo è partizionare entrambi gli array in modo che tutti gli elementi sul lato sinistro siano minori o uguali a tutti gli elementi sul lato destro, e il numero di elementi sul lato sinistro sia uguale (o uno in più) a quello sul lato destro. La mediana può quindi essere calcolata dagli elementi di confine.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Ensure nums1 is smaller 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('Input arrays are not sorted'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

Risolutore di Sudoku

Scrivi un programma per risolvere un puzzle di Sudoku riempiendo le celle vuote.

Spiegazione: Usa il backtracking. Trova una cella vuota. Prova a riempirla con numeri da 1 a 9. Per ogni numero, controlla se è valido (non viola le regole del Sudoku). Se valido, chiama ricorsivamente il risolutore. Se la ricorsione restituisce true, è stata trovata una soluzione. In caso contrario, torna indietro (reimposta la cella) e prova il numero successivo.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Check row if (board[i][col] === numStr) return false; // Check col 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; // Check box } 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] = '.'; // Backtrack } } return false; // No valid number found } } } return true; // Board solved } backtrack(); return board; } // Example requires a 9x9 board with '.' for empty cells.

Implementa un Pattern Middleware di Base

Implementa un semplice pattern middleware spesso visto nei framework web.

Spiegazione: Le funzioni middleware elaborano una richiesta prima che raggiunga il gestore finale. Ogni middleware può modificare la richiesta/risposta o passare il controllo al middleware next. Crea una classe o un oggetto che gestisce un elenco di funzioni middleware e le esegue in sequenza.

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: Logging...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Authenticating...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Final Handler:', req); }); app.handle({ data: 'some request' }); // 1: Logging... // 2: Authenticating... // 3: Final Handler: { data: 'some request', logged: true, authed: true }

Rileva Ciclo nel Grafo Orientato

Dato un grafo orientato, scrivi una funzione per determinare se contiene un ciclo.

Spiegazione: Usa la ricerca in profondità (DFS). Mantieni due insiemi: visiting (nodi attualmente nello stack di ricorsione) e visited (nodi che sono stati completamente esplorati). Se incontri un nodo nell'insieme visiting durante la DFS, hai trovato un arco di ritorno, il che significa che c'è un 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; // Cycle detected 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

Implementa il Comportamento di Object.freeze

Spiega Object.freeze e implementa una versione (shallow).

Spiegazione: Object.freeze impedisce l'aggiunta di nuove proprietà, la rimozione di proprietà esistenti e la modifica di proprietà esistenti o della loro enumerabilità, configurabilità o scrivibilità. Rende un oggetto immutabile (in modo shallow). Puoi implementarlo usando Object.preventExtensions e Object.defineProperty per rendere le proprietà esistenti non scrivibili e non configurabili.

function myFreeze(obj) { Object.preventExtensions(obj); // Prevent adding new properties 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; // Fails silently (or throws in strict mode) // delete obj.a; // Fails silently (or throws in strict mode) // obj.a = 10; // Fails silently (or throws in strict mode) console.log(obj); // { a: 1, b: 2 }

Usa requestAnimationFrame per Animazioni Fluide

Spiega requestAnimationFrame e mostra come usarlo per un semplice ciclo di animazione.

Spiegazione: requestAnimationFrame (rAF) dice al browser che desideri eseguire un'animazione e richiede che il browser chiami una funzione specificata per aggiornare un'animazione prima del prossimo repaint. È più efficiente e fluido dell'uso di setTimeout o setInterval per le animazioni, poiché si sincronizza con la frequenza di aggiornamento del browser e si mette in pausa quando la scheda non è visibile.

// 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; // Move 100px in 2 seconds (50px/sec) position = (elapsedTime / 2000) * 100; if (position < 200) { // Keep animating until it reaches 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Start the animation // requestAnimationFrame(animate); // Uncomment to run in browser console.log('Use requestAnimationFrame for browser animations.');

Implementa Array.prototype.some

Implementa la tua versione di Array.prototype.some.

Spiegazione: some verifica se almeno un elemento nell'array supera il test implementato dalla funzione fornita. Restituisce true se trova un elemento per cui il callback restituisce true; altrimenti, restituisce false.

function mySome(arr, callback, thisArg) { for (let i = 0; i < arr.length; i++) { if (i in arr) { // Handle sparse arrays 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

Implementa Array.prototype.every

Implementa la tua versione di Array.prototype.every.

Spiegazione: every verifica se tutti gli elementi nell'array superano il test implementato dalla funzione fornita. Restituisce true se tutti gli elementi superano (o se l'array è vuoto), e false se un qualsiasi elemento fallisce.

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

Implementa Array.prototype.findIndex

Implementa la tua versione di Array.prototype.findIndex.

Spiegazione: findIndex restituisce l'indice del primo elemento nell'array che soddisfa la funzione di test fornita. Altrimenti, restituisce -1, indicando che nessun elemento ha superato il test.

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

Cosa sono i Web Workers?

Spiega cosa sono i Web Workers e il loro caso d'uso principale.

Spiegazione: I Web Workers sono un mezzo per il contenuto web per eseguire script in thread in background. Il caso d'uso principale è quello di scaricare attività a lungo termine o computazionalmente intensive dal thread principale (che gestisce gli aggiornamenti dell'interfaccia utente e le interazioni con l'utente), prevenendo così che l'interfaccia utente diventi non responsiva o 'si blocchi'. I Worker comunicano con il thread principale tramite passaggio di messaggi (postMessage e onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Send data to worker myWorker.onmessage = function(e) { console.log('Result from worker:', e.data); } } else { console.log('Your browser does not support Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Message received from main script'); const result = e.data[0] * e.data[1]; console.log('Posting result back to main script'); self.postMessage(result); } */ console.log('Web Workers run scripts in background threads.');

Decodifica Modi

Un messaggio contenente lettere dalla A-Z può essere codificato in numeri usando la mappatura 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26. Data una stringa s contenente solo cifre, restituisci il numero di modi per decodificarla.

Spiegazione: Usa la programmazione dinamica. Sia dp[i] il numero di modi per decodificare s[0...i-1]. dp[i] dipende da dp[i-1] (se s[i-1] è un codice valido di 1 cifra) e dp[i-2] (se s[i-2...i-1] è un codice valido di 2 cifre).

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' or 'L') console.log(numDecodings('226')); // 3 ('BBF', 'BZ', 'VF') console.log(numDecodings('06')); // 0

Addizione Bitwise (senza +)

Scrivi una funzione per sommare due interi senza usare l'operatore +.

Spiegazione: Usa gli operatori bitwise. La somma può essere calcolata come a ^ b (somma senza riporto), e il riporto può essere calcolato come (a & b) << 1. Ripeti questo processo finché il riporto diventa 0.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // Calculate carry a = a ^ b; // Calculate sum without carry b = carry; // Carry becomes the new 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

Verifica Palindromo (Numero)

Dato un intero x, restituisci true se x è un palindromo, e false altrimenti.

Spiegazione: Un numero negativo non è un palindromo. Converti il numero in una stringa e controlla se la stringa è un palindromo (si legge allo stesso modo in avanti e all'indietro). In alternativa, inverti il numero matematicamente e confronta.

function isPalindromeNumber(x) { if (x < 0) return false; // String approach // return String(x) === String(x).split('').reverse().join(''); // Mathematical approach 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

Pattern Factory

Implementa il pattern di progettazione Factory in JavaScript.

Spiegazione: Il pattern Factory fornisce un'interfaccia per la creazione di oggetti in una superclasse, ma consente alle sottoclassi di alterare il tipo di oggetti che verranno creati. È utile per creare oggetti senza specificare la classe esatta.

class Car { constructor(options) { this.type = 'Car'; this.doors = options.doors || 4; } } class Truck { constructor(options) { this.type = 'Truck'; this.bedSize = options.bedSize || 'long'; } } class VehicleFactory { createVehicle(options) { if (options.vehicleType === 'car') { return new Car(options); } else if (options.vehicleType === 'truck') { return new Truck(options); } else { throw new Error('Unknown vehicle type'); } } } const factory = new VehicleFactory(); const car = factory.createVehicle({ vehicleType: 'car', doors: 2 }); const truck = factory.createVehicle({ vehicleType: 'truck', bedSize: 'short' }); console.log(car); // Car { type: 'Car', doors: 2 } console.log(truck); // Truck { type: 'Truck', bedSize: 'short' }

Spiega Symbol e un Caso d'Uso

Spiega cos'è Symbol in JavaScript e fornisce un caso d'uso, come la creazione di proprietà 'private' o chiavi di oggetti uniche.

Spiegazione: Symbol è un tipo di dato primitivo introdotto in ES6. Le sue istanze sono uniche e immutabili. Sono spesso usate come chiavi per le proprietà degli oggetti per evitare collisioni di nomi tra diverse librerie o per creare proprietà pseudo-private (anche se non veramente private, non sono accessibili tramite l'iterazione tipica o Object.keys).

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Hello, my name is ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Hello, my name is Bob console.log(Object.keys(p)); // [] - Symbols are not included console.log(p[_privateName]); // Bob (Accessible if you have the Symbol)

Converti arguments in un Vero Array

Come puoi convertire l'oggetto arguments (disponibile nelle funzioni non freccia) in un vero array JavaScript?

Spiegazione: L'oggetto arguments sembra un array ma non lo è; gli mancano metodi come map, filter, ecc. Puoi convertirlo usando:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (Sintassi Spread ES6)
  3. Array.prototype.slice.call(arguments) (Modo più vecchio)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Using Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread Syntax const args2 = [...arguments]; console.log('Using Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Using Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Using Array.from: 10 // Using Spread: 10 // Using Slice: 10