Live Coding

Sprawdź, czy obiekt jest pusty

Jak sprawdzić, czy obiekt JavaScript jest pusty?

Wyjaśnienie: Obiekt jest pusty, jeśli nie ma żadnych własnych, wyliczalnych właściwości. Możemy użyć Object.keys(obj), która zwraca tablicę nazw własnych, wyliczalnych właściwości danego obiektu. Jeśli długość tej tablicy wynosi 0, obiekt jest pusty.

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

Odwróć ciąg znaków

Napisz funkcję, która odwraca dany ciąg znaków.

Wyjaśnienie: Najprostszym sposobem jest przekształcenie ciągu znaków w tablicę znaków, użycie wbudowanej metody reverse() dla tablic, a następnie połączenie znaków z powrotem w ciąg znaków.

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

Sprawdzenie palindromu

Napisz funkcję, która sprawdza, czy dany ciąg znaków jest palindromem.

Wyjaśnienie: Palindrom to słowo lub fraza, która czyta się tak samo do przodu i do tyłu. Możemy to sprawdzić, odwracając ciąg znaków (ignorując wielkość liter i znaki niealfanumeryczne dla bardziej solidnego sprawdzenia, ale tutaj wykonujemy proste sprawdzenie) i porównując go z oryginałem.

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

Znajdź największą liczbę w tablicy

Napisz funkcję, która znajduje największą liczbę w tablicy liczb.

Wyjaśnienie: Możesz iterować po tablicy, śledząc największą znalezioną do tej pory liczbę. Alternatywnie, możesz użyć funkcji Math.max() wraz ze składnią spread (...), aby przekazać elementy tablicy jako argumenty.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // Lub rzuć błąd return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Napisz program, który wypisuje liczby od 1 do n. Ale dla wielokrotności trzech wypisz 'Fizz' zamiast liczby, a dla wielokrotności pięciu wypisz 'Buzz'. Dla liczb będących wielokrotnością zarówno trzech, jak i pięciu, wypisz 'FizzBuzz'.

Wyjaśnienie: Ten klasyczny problem testuje podstawową logikę pętli i warunków. Musisz iterować od 1 do n i użyć operatora modulo (%) do sprawdzenia podzielności przez 3 i 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);

Usuń duplikaty z tablicy

Napisz funkcję, która usuwa z tablicy zduplikowane elementy.

Wyjaśnienie: Nowoczesny i zwięzły sposób na osiągnięcie tego to użycie Set. Zestawy przechowują tylko unikalne wartości. Możesz przekonwertować tablicę na Set, a następnie z powrotem na tablicę.

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

Sprawdzenie anagramu

Napisz funkcję, która sprawdza, czy dwa ciągi znaków są anagramami.

Wyjaśnienie: Anagramy to słowa utworzone przez przestawienie liter innego słowa. Aby to sprawdzić, możemy wyczyścić, posortować i porównać ciągi znaków. Czyszczenie obejmuje usunięcie znaków niealfanumerycznych i konwersję na jednolitą wielkość liter.

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

Obliczanie silni

Napisz funkcję do obliczania silni nieujemnej liczby całkowitej.

Wyjaśnienie: Silnia (n!) liczby to iloczyn wszystkich dodatnich liczb całkowitych mniejszych lub równych n. Możemy to obliczyć iteracyjnie, mnożąc liczby od 1 do n.

function factorial(n) { if (n < 0) return undefined; // Silnia nie jest zdefiniowana dla liczb ujemnych if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Sumuj wszystkie liczby w tablicy

Napisz funkcję, która zwraca sumę wszystkich liczb w tablicy.

Wyjaśnienie: Możesz użyć metody reduce, która jest idealna do kumulowania pojedynczej wartości z tablicy. Przyjmuje ona funkcję zwrotną i wartość początkową (0 dla sumowania).

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

Spłaszcz zagnieżdżoną tablicę

Napisz funkcję spłaszczającą zagnieżdżoną tablicę. Dla uproszczenia załóż tylko jeden poziom zagnieżdżenia.

Wyjaśnienie: Dla pojedynczego poziomu zagnieżdżenia możesz użyć Array.prototype.concat() z operatorem spread lub metody flat() (ES2019).

function flattenArray(arr) { // Użycie flat() (prostsze, jeśli ES2019+ jest w porządku) // return arr.flat(); // Użycie reduce i concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Policz samogłoski w ciągu znaków

Napisz funkcję, która zlicza liczbę samogłosek (a, e, i, o, u) w danym ciągu znaków.

Wyjaśnienie: Iteruj po ciągu znaków (bez uwzględniania wielkości liter) i sprawdzaj, czy każdy znak jest samogłoską. Utrzymuj licznik.

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

Zamień zdanie na tytułowe

Napisz funkcję, która konwertuje zdanie na tytułowe (pierwsza litera każdego słowa jest pisana wielką literą).

Wyjaśnienie: Podziel zdanie na słowa, a następnie iteruj po każdym słowie, pisząc pierwszą literę wielką literą, a resztę małymi literami. Na koniec połącz słowa z powrotem.

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'

Problem dwóch sum

Mając tablicę liczb całkowitych nums i liczbę całkowitą target, zwróć indeksy dwóch liczb, które sumują się do target.

Wyjaśnienie: Powszechnym podejściem jest użycie mapy haszującej (lub obiektu JavaScript) do przechowywania liczb i ich indeksów podczas iteracji. Dla każdej liczby sprawdź, czy target - bieżąca_liczba istnieje w mapie.

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 []; // Lub null, lub rzuć błąd, jeśli brak rozwiązania } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Zaimplementuj licznik za pomocą domknięć

Utwórz funkcję, która zwraca funkcję licznika. Za każdym razem, gdy wywołana zostanie zwrócona funkcja, powinna ona zwiększać i zwracać wartość licznika.

Wyjaśnienie: Demonstracja domknięć. Funkcja wewnętrzna ma dostęp do zmiennej count w zakresie funkcji zewnętrznej, nawet po zakończeniu wykonywania funkcji zewnętrznej.

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

Znajdź pierwszy niepowtarzający się znak

Napisz funkcję, która znajduje pierwszy niepowtarzający się znak w ciągu znaków.

Wyjaśnienie: Możesz użyć mapy haszującej do zliczania częstotliwości znaków. Najpierw iteruj po ciągu znaków, aby zbudować mapę częstotliwości. Następnie ponownie iteruj po ciągu znaków i zwróć pierwszy znak z licznikiem 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; // Lub jakiś wskaźnik, jeśli wszystkie się powtarzają } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Dzielenie tablicy na fragmenty

Napisz funkcję, która dzieli tablicę na grupy o określonym rozmiarze.

Wyjaśnienie: Iteruj po tablicy, pobierając wycinki o określonym rozmiarze i dodając je do nowej tablicy. Obsłuż przypadek, gdy ostatni fragment może być mniejszy.

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

Sprawdź, czy liczba jest liczbą pierwszą

Napisz funkcję, która określa, czy dana liczba jest liczbą pierwszą.

Wyjaśnienie: Liczba pierwsza to liczba naturalna większa od 1, która ma dokładnie dwa dzielniki naturalne: jedynkę i samą siebie. Iteruj od 2 do pierwiastka kwadratowego z liczby, sprawdzając podzielność. Obsłuż przypadki brzegowe, takie jak 1 i 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

Znajdź najdłuższe słowo w ciągu znaków

Napisz funkcję, która znajduje najdłuższe słowo w zdaniu.

Wyjaśnienie: Podziel ciąg znaków na tablicę słów. Następnie iteruj po tablicy, śledząc najdłuższe znalezione do tej pory słowo (lub jego długość).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Wyczyść słowa, jeśli to konieczne (np. usuń znaki interpunkcyjne) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

Zaimplementuj `Array.prototype.map`

Zaimplementuj własną wersję funkcji Array.prototype.map.

Wyjaśnienie: Funkcja map przyjmuje callback i tworzy nową tablicę, stosując callback do każdego elementu oryginalnej tablicy. Twoja funkcja powinna iterować po tablicy i budować nową tablicę.

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]

Zaimplementuj `Array.prototype.filter`

Zaimplementuj własną wersję funkcji Array.prototype.filter.

Wyjaśnienie: Funkcja filter przyjmuje callback i tworzy nową tablicę ze wszystkimi elementami, które przejdą test zaimplementowany przez dostarczoną funkcję callback.

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

Zaimplementuj `Array.prototype.reduce`

Zaimplementuj własną wersję funkcji Array.prototype.reduce.

Wyjaśnienie: Funkcja reduce wykonuje dostarczoną przez użytkownika funkcję zwrotną 'reducer' na każdym elemencie tablicy, przekazując wartość zwracaną z obliczeń na poprzednim elemencie. Końcowym wynikiem uruchomienia reducera na wszystkich elementach tablicy jest pojedyncza wartość. Obsłuż argument wartości początkowej.

function myReduce(arr, callback, initialValue) { let accumulator = initialValue; let startIndex = 0; if (initialValue === undefined) { if (arr.length === 0) throw new TypeError('Zmniejszenie pustej tablicy bez wartości początkowej'); 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

Memoizacja - Ciąg Fibonacciego

Zaimplementuj funkcję Fibonacciego używając memoizacji w celu optymalizacji wydajności.

Wyjaśnienie: Ciąg Fibonacciego wiąże się z powtarzającymi się obliczeniami. Memoizacja przechowuje wyniki kosztownych wywołań funkcji i zwraca buforowany wynik, gdy ponownie wystąpią te same dane wejściowe.

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 (znacznie szybciej niż bez memoizacji)

Sprawdź, czy nawiasy są zbalansowane

Napisz funkcję, która sprawdza, czy ciąg znaków zawierający nawiasy {}[]() jest zbalansowany.

Wyjaśnienie: Użyj stosu. Kiedy napotkasz otwierający nawias, wciśnij go na stos. Kiedy napotkasz zamykający nawias, sprawdź, czy stos jest pusty lub czy górny element jest pasującym nawiasem otwierającym. Jeśli pasuje, zdejmij ze stosu. Jeśli nie, lub jeśli stos jest pusty, jest niezbalansowany. Na końcu, pusty stos oznacza zbalansowany.

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

Zaimplementuj prostą kolejkę

Zaimplementuj strukturę danych Kolejka z metodami enqueue (dodaj na koniec) i dequeue (usuń z początku).

Wyjaśnienie: Kolejka działa na zasadzie First-In, First-Out (FIFO). Można użyć tablicy, z push dla enqueue i shift dla dequeue.

class Queue { constructor() { this.items = []; } enqueue(element) { this.items.push(element); } dequeue() { if (this.isEmpty()) return 'Niedopełnienie'; return this.items.shift(); } front() { if (this.isEmpty()) return 'Brak elementów w Kolejce'; 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

Zaimplementuj prosty stos

Zaimplementuj strukturę danych stosu z metodami push (dodaj na górę) i pop (usuń z góry).

Wyjaśnienie: Stos działa na zasadzie Last-In, First-Out (LIFO). Można użyć tablicy z metodami push i pop.

class Stack { constructor() { this.items = []; } push(element) { this.items.push(element); } pop() { if (this.isEmpty()) return 'Niedopełnienie'; 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

Znajdź brakującą liczbę w sekwencji

Mając tablicę zawierającą n różnych liczb pobranych z 0, 1, 2, ..., n, znajdź tę, której brakuje w tablicy.

Wyjaśnienie: Sumę liczb od 0 do n można obliczyć za pomocą wzoru n*(n+1)/2. Można obliczyć rzeczywistą sumę elementów tablicy. Różnica między tymi dwoma sumami będzie brakującą liczbą.

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

Funkcja Debounce

Zaimplementuj funkcję debounce. Debouncing zapewnia, że funkcja nie zostanie wywołana ponownie, dopóki nie upłynie określony czas, w którym nie była wywoływana.

Wyjaśnienie: Użyj setTimeout i clearTimeout. Za każdym razem, gdy wywoływana jest debounced funkcja, wyczyść poprzednie opóźnienie i ustaw nowe. Faktyczne wywołanie funkcji następuje dopiero po zakończeniu opóźnienia.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Przykład użycia: const sayHello = () => console.log('Witaj!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Wywołane po 1 sek (jeśli nie wywołane ponownie) debouncedHello(); // Resetuje timer debouncedHello(); // Resetuje timer, faktycznie uruchomi się 1 sek po tym wywołaniu.

Funkcja Throttle

Zaimplementuj funkcję throttle. Throttling zapewnia, że funkcja jest wywoływana co najwyżej raz w określonym przedziale czasowym.

Wyjaśnienie: Użyj flagi, aby wskazać, czy wywołanie jest dozwolone. Po wywołaniu, jeśli dozwolone, wykonaj funkcję, ustaw flagę na false i użyj setTimeout, aby zresetować flagę po upływie interwału.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Przykład użycia: const logScroll = () => console.log('Przewijanie...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Będzie logować co najwyżej raz na sekundę.

Funkcja Currying

Napisz funkcję, która przyjmuje funkcję z dwoma argumentami i zwraca jej scurryfikowaną wersję.

Wyjaśnienie: Currying przekształca funkcję z wieloma argumentami w sekwencję funkcji, z których każda przyjmuje pojedynczy argument. f(a, b) staje się 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

Głębokie klonowanie obiektu

Napisz funkcję do wykonania głębokiego klonowania obiektu JavaScript.

Wyjaśnienie: Płytkie klonowanie kopiuje tylko właściwości najwyższego poziomu. Głębokie klonowanie rekurencyjnie kopiuje wszystkie właściwości, w tym zagnieżdżone obiekty i tablice. Prostym sposobem (z ograniczeniami, np. nie obsługuje dobrze funkcji, dat, wyrażeń regularnych) jest użycie JSON.parse(JSON.stringify(obj)). Rekurencyjne podejście jest bardziej niezawodne.

function deepClone(obj) { // Prosta wersja (z ograniczeniami) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Klonowanie nie powiodło się:", e); return null; } // Bardziej niezawodne rekurencyjne (podstawowy przykład): /* 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 (dowodzi, że jest to głębokie klonowanie) console.log(cloned.b.c); // 3

Pobierz klucze obiektu

Jak pobrać tablicę kluczy z obiektu?

Wyjaśnienie: Użyj Object.keys(obj).

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

Pobierz wartości obiektu

Jak pobrać tablicę wartości z obiektu?

Wyjaśnienie: Użyj Object.values(obj).

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

Sprawdź, czy tablica zawiera wartość

Jak sprawdzić, czy tablica zawiera określoną wartość?

Wyjaśnienie: Użyj Array.prototype.includes(value).

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

Scal dwie tablice

Jak scalić dwie tablice w jedną?

Wyjaśnienie: Użyj składni spread (...) lub Array.prototype.concat().

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

Wyjaśnij 'this' w zasięgu globalnym

Do czego odnosi się this w zasięgu globalnym w przeglądarce?

Wyjaśnienie: W globalnym kontekście wykonania (poza jakąkolwiek funkcją), this odnosi się do obiektu globalnego, którym w przeglądarkach internetowych jest window.

console.log(this === window); // true (w środowisku przeglądarki)

Wyjaśnij 'this' w metodzie obiektu

Do czego odnosi się this, gdy jest używane w metodzie obiektu?

Wyjaśnienie: Kiedy funkcja jest wywoływana jako metoda obiektu, this odnosi się do obiektu, na którym metoda jest wywoływana.

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

Wyjaśnij 'this' w funkcjach strzałkowych

Jak funkcje strzałkowe obsługują this?

Wyjaśnienie: Funkcje strzałkowe nie mają własnego kontekstu this. Zamiast tego dziedziczą this z otaczającego (leksykalnego) zasięgu, w którym są zdefiniowane.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Loguje 42, ponieważ 'this' jest dziedziczone }, 100); } new MyClass();

Różnice między `let`, `const` i `var`

Jakie są główne różnice między let, const i var?

Wyjaśnienie: var ma zakres funkcji i jest podnoszony (hoisting). let i const mają zakres bloku i są podnoszone, ale znajdują się w 'tymczasowej strefie martwej' do momentu deklaracji. const musi być zainicjowane i nie może być ponownie przypisane.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Ponownie deklaruje i wpływa na zewnętrzne 'a' let b = 20; // Nowe 'b' wewnątrz bloku // const c = 30; // Byłoby to nowe 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

Czym jest Obietnica?

Wyjaśnij, czym jest Obietnica w JavaScript.

Wyjaśnienie: Obietnica to obiekt reprezentujący ostateczne zakończenie (lub niepowodzenie) operacji asynchronicznej i jej wynikową wartość. Może znajdować się w jednym z trzech stanów: oczekująca, spełniona lub odrzucona.

const myPromise = new Promise((resolve, reject) => { setTimeout(() => { resolve('Sukces!'); // reject('Błąd!'); }, 1000); }); myPromise .then(result => console.log(result)) .catch(error => console.error(error));

Użycie `async/await`

Jak async i await upraszczają pracę z Obietnicami?

Wyjaśnienie: async/await dostarcza cukier składniowy nad Obietnicami, dzięki czemu kod asynchroniczny wygląda i zachowuje się nieco bardziej jak kod synchroniczny. Funkcja async zawsze zwraca Obietnicę, a await wstrzymuje wykonanie, dopóki Obietnica nie zostanie rozstrzygnięta.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('Startuję...'); await delay(1000); console.log('Czekałem 1 sekundę.'); await delay(500); console.log('Czekałem kolejne 0.5 sekundy.'); return 'Zakończono!'; } run().then(console.log);

Konwertuj ciąg znaków na liczbę

Jak przekonwertować ciąg znaków na liczbę?

Wyjaśnienie: Użyj parseInt(), parseFloat() lub operatora unarnego plus (+).

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

Konwertuj liczbę na ciąg znaków

Jak przekonwertować liczbę na ciąg znaków?

Wyjaśnienie: Użyj String(), number.toString() lub konkatenacji ciągów znaków.

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

Czym jest `JSON.stringify`?

Co robi JSON.stringify?

Wyjaśnienie: Konwertuje obiekt lub wartość JavaScript na ciąg znaków JSON.

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

Czym jest `JSON.parse`?

Co robi JSON.parse?

Wyjaśnienie: Parsuje ciąg znaków JSON, konstruując wartość lub obiekt JavaScript opisany przez ciąg znaków.

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

Operator spread w tablicach

Jak używany jest operator spread w tablicach?

Wyjaśnienie: Pozwala na rozszerzanie iterowalnego (takiego jak tablica) w miejscach, gdzie oczekuje się zera lub więcej argumentów lub elementów. Przydatne do kopiowania, scalania i dodawania elementów.

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

Operator spread w obiektach

Jak używany jest operator spread z obiektami?

Wyjaśnienie: Pozwala na kopiowanie i scalanie właściwości obiektów.

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 }

Destrukturyzacja tablic

Wyjaśnij destrukturyzację tablic na przykładzie.

Wyjaśnienie: Jest to składnia, która umożliwia rozpakowywanie wartości z tablic do odrębnych zmiennych.

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

Destrukturyzacja obiektów

Wyjaśnij destrukturyzację obiektów na przykładzie.

Wyjaśnienie: Umożliwia rozpakowywanie właściwości z obiektów do odrębnych zmiennych.

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'

Zaimplementuj `call`

Jak można by zaimplementować podstawową wersję Function.prototype.call?

Wyjaśnienie: call wywołuje funkcję z określoną wartością this i argumentami dostarczonymi indywidualnie. Możesz dołączyć funkcję do kontekstu this, wywołać ją, a następnie usunąć.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Użyj unikalnego klucza 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' }, 'Cześć', '!'); // Cześć, Charlie!

Zaimplementuj `apply`

Jak można by zaimplementować podstawową wersję Function.prototype.apply?

Wyjaśnienie: apply jest podobne do call, ale akceptuje argumenty jako tablicę.

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' }, ['Witaj', '.']); // Witaj, David.

Wyjaśnienie pętli zdarzeń (Event Loop)

Krótko wyjaśnij pętlę zdarzeń JavaScript. Wyjaśnienie: JavaScript jest jednowątkowy, ale osiąga współbieżność za pomocą pętli zdarzeń. Stos wywołań (call stack) obsługuje kod synchroniczny. Web API obsługują operacje asynchroniczne (takie jak setTimeout, fetch). Gdy operacja asynchroniczna się zakończy, jej funkcja zwrotna trafia do kolejki funkcji zwrotnych (callback queue) (lub kolejki mikro-zadań dla Promise). Pętla zdarzeń stale sprawdza, czy stos wywołań jest pusty; jeśli tak, przenosi następną funkcję zwrotną z kolejki na stos w celu wykonania.

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)

Wyszukiwanie binarne

Zaimplementuj funkcję wyszukiwania binarnego dla posortowanej tablicy. Wyjaśnienie: Wyszukiwanie binarne efektywnie znajduje element w posortowanej tablicy, wielokrotnie dzieląc przedział wyszukiwania na pół. Jeśli wartość klucza wyszukiwania jest mniejsza niż element w środku przedziału, zawęź przedział do dolnej połowy. W przeciwnym razie zawęź go do górnej połowy. Robisz to, dopóki wartość nie zostanie znaleziona lub przedział nie będzie pusty.

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

Sortowanie przez scalanie (Merge Sort)

Zaimplementuj algorytm sortowania przez scalanie (Merge Sort). Wyjaśnienie: Sortowanie przez scalanie to algorytm typu dziel i zwyciężaj. Dzieli on tablicę wejściową na dwie połówki, wywołuje się dla tych dwóch połówek, a następnie scala dwie posortowane połówki. Funkcja scalająca jest kluczowa; łączy ona dwie posortowane podtablice w jedną posortowaną tablicę.

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]

Sortowanie szybkie (Quick Sort)

Zaimplementuj algorytm sortowania szybkiego (Quick Sort). Wyjaśnienie: Sortowanie szybkie to również algorytm typu dziel i zwyciężaj. Wybiera element jako punkt obrotu i dzieli daną tablicę wokół wybranego punktu obrotu. Elementy mniejsze od punktu obrotu trafiają na lewą stronę, a elementy większe na prawą. Następnie rekurencyjnie sortuje podtablice.

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]

Sortowanie bąbelkowe (Bubble Sort)

Zaimplementuj algorytm sortowania bąbelkowego (Bubble Sort). Wyjaśnienie: Sortowanie bąbelkowe to prosty algorytm sortowania, który wielokrotnie przechodzi przez listę, porównuje sąsiadujące elementy i zamienia je miejscami, jeśli są w złej kolejności. Przejście przez listę jest powtarzane, aż lista zostanie posortowana.

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]

Najdłuższy podciąg bez powtarzających się znaków

Dla danego ciągu znaków znajdź długość najdłuższego podciągu bez powtarzających się znaków. Wyjaśnienie: Użyj techniki 'ruchomego okna'. Utrzymuj okno (podciąg) i zestaw znaków w tym oknie. Rozszerzaj okno, przesuwając prawy wskaźnik. Jeśli zostanie znaleziony powtarzający się znak, zmniejsz okno od lewej, aż powtarzający się znak zostanie usunięty.

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

Implementacja listy jednokierunkowej

Zaimplementuj listę jednokierunkową z metodami add i print. Wyjaśnienie: Lista połączona to liniowa struktura danych, w której elementy nie są przechowywane w ciągłych lokalizacjach pamięci. Każdy element (węzeł) wskazuje na następny. Potrzebujesz klasy Node i klasy LinkedList do zarządzania głową i dodawania nowych węzłów.

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

Implementacja binarnego drzewa poszukiwań (BST)

Zaimplementuj binarne drzewo poszukiwań (BST) z metodami insert i find. Wyjaśnienie: BST to struktura danych drzewa binarnego oparta na węzłach, która ma następujące właściwości: Lewe poddrzewo węzła zawiera tylko węzły z kluczami mniejszymi niż klucz węzła. Prawe poddrzewo węzła zawiera tylko węzły z kluczami większymi niż klucz węzła. Oba poddrzewa muszą być również binarnymi drzewami poszukiwań.

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

Obracanie tablicy

Napisz funkcję, która obraca tablicę w prawo o k kroków. Wyjaśnienie: Obracanie tablicy oznacza przesuwanie jej elementów. Dla obrotu w prawo, ostatni element staje się pierwszym. Powtarzanie tego k razy działa, ale jest nieefektywne. Lepszym sposobem jest użycie manipulacji tablicą lub obliczenie nowych pozycji.

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]

Znajdź przecięcie dwóch tablic

Dla dwóch danych tablic, napisz funkcję, która obliczy ich przecięcie (elementy wspólne dla obu). Wyjaśnienie: Dobrym podejściem jest przekształcenie jednej tablicy w Set dla wyszukiwania w czasie O(1). Następnie, iteruj przez drugą tablicę i sprawdź, czy każdy element istnieje w zestawie. Zbierz pasujące elementy.

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]

Grupowanie anagramów

Dla danej tablicy ciągów znaków, pogrupuj anagramy. Wyjaśnienie: Kluczem jest znalezienie unikalnej sygnatury dla każdej grupy anagramów. Typowym sposobem jest posortowanie każdego słowa alfabetycznie. Słowa, które po posortowaniu dają ten sam ciąg znaków, są anagramami. Użyj mapy hash, gdzie kluczami są posortowane słowa, a wartościami są tablice oryginalnych słów.

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

Przesunięcie zer na koniec

Dla danej tablicy nums, napisz funkcję, która przeniesie wszystkie zera na jej koniec, zachowując względną kolejność elementów niezerowych. Wyjaśnienie: Użyj podejścia 'dwóch wskaźników' lub 'kuli śnieżnej'. Jeden wskaźnik śledzi pozycję, na której powinien znaleźć się następny element niezerowy. Iteruj przez tablicę; jeśli element jest niezerowy, umieść go na pozycji wskaźnika i zwiększ wskaźnik. Na koniec, wypełnij resztę zerami.

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]

Maksymalna suma podtablicy (algorytm Kadane'a)

Dla danej tablicy liczb całkowitych nums, znajdź ciągłą podtablicę (zawierającą co najmniej jedną liczbę), która ma największą sumę i zwróć tę sumę. Wyjaśnienie: Algorytm Kadane'a to efektywny sposób rozwiązania tego problemu. Iteruj przez tablicę, śledząc sumę currentMax kończącą się na bieżącej pozycji i sumę globalMax znalezioną do tej pory. Jeśli currentMax stanie się ujemna, zresetuj ją do 0 (lub raczej do bieżącego elementu).

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

Permutacje ciągu znaków

Napisz funkcję, która generuje wszystkie permutacje danego ciągu znaków. Wyjaśnienie: Problem ten jest typowo rozwiązywany za pomocą rekurencji i przeszukiwania z nawrotami. Dla każdego znaku, ustal go i rekurencyjnie generuj permutacje dla reszty ciągu. Przypadek podstawowy: gdy ciąg ma tylko jeden znak, zwróć go.

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

Największy wspólny dzielnik (NWD)

Napisz funkcję, która znajduje największy wspólny dzielnik (NWD) dwóch liczb. Wyjaśnienie: Algorytm Euklidesa to efektywna metoda. Jeśli b jest 0, a jest NWD. W przeciwnym razie, NWD z a i b jest takie samo jak NWD z b i a % b (reszta z dzielenia a przez 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

Najmniejsza wspólna wielokrotność (NWW)

Napisz funkcję, która znajduje najmniejszą wspólną wielokrotność (NWW) dwóch liczb. Wyjaśnienie: NWW można obliczyć za pomocą NWD ze wzoru: NWW(a, b) = |a * b| / NWD(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

Implementacja Promise.all

Zaimplementuj funkcję, która działa jak Promise.all. Wyjaśnienie: Promise.all przyjmuje tablicę obietnic i zwraca pojedynczą obietnicę, która rozwiązuje się, gdy wszystkie obietnice wejściowe zostaną rozwiązane, lub odrzuca się, jeśli którakolwiek obietnica wejściowa zostanie odrzucona. Rozwiązana wartość to tablica rozwiązanych wartości.

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

Odwróć drzewo binarne

Napisz funkcję, która odwraca drzewo binarne. Wyjaśnienie: Aby odwrócić drzewo binarne, zamień lewe i prawe dzieci dla każdego węzła. Można to zrobić rekurencyjnie lub iteracyjnie (przy użyciu kolejki lub stosu).

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]

Maksymalna głębokość drzewa binarnego

Dla danego drzewa binarnego znajdź jego maksymalną głębokość. Wyjaśnienie: Maksymalna głębokość to liczba węzłów w najdłuższej ścieżce od węzła korzenia do najdalszego węzła liścia. Można to znaleźć rekurencyjnie: MaxDepth = 1 + max(MaxDepth(left), MaxDepth(right)). Przypadek podstawowy to, gdy węzeł jest nullem, jego głębokość wynosi 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

Najlepszy czas na kupno/sprzedaż akcji

Masz daną tablicę prices, gdzie prices[i] to cena danej akcji i-tego dnia. Chcesz zmaksymalizować swój zysk, wybierając jeden dzień na kupno jednej akcji i inny dzień w przyszłości na sprzedaż tej akcji. Zwróć maksymalny zysk. Wyjaśnienie: Iteruj przez ceny, śledząc do tej pory minimalną napotkaną cenę (minPrice) i maksymalny do tej pory zysk (maxProfit). Dla każdego dnia oblicz potencjalny zysk, jeśli sprzedasz dzisiaj (aktualna cena - minPrice) i zaktualizuj maxProfit, jeśli jest wyższy.

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)

Pojedyncza liczba

Dla danej niepustej tablicy liczb całkowitych nums, każdy element pojawia się dwa razy, z wyjątkiem jednego. Znajdź ten pojedynczy element. Wyjaśnienie: Bardzo efektywnym sposobem rozwiązania tego problemu jest użycie operatora bitowego XOR (^). XORowanie liczby z samą sobą daje 0. XORowanie liczby z 0 daje samą liczbę. Jeśli wykonasz XOR na wszystkich liczbach w tablicy, pary się zneutralizują (staną się 0), pozostawiając tylko pojedynczą liczbę.

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

Element większościowy

Dla danej tablicy nums o rozmiarze n, zwróć element większościowy. Element większościowy to element, który pojawia się więcej niż ⌊n / 2⌋ razy. Wyjaśnienie: Algorytm głosowania Boyer-Moore'a jest efektywnym sposobem. Zainicjuj candidate i count. Iteruj przez tablicę. Jeśli count wynosi 0, ustaw bieżący element jako candidate. Jeśli bieżący element pasuje do candidate, zwiększ count; w przeciwnym razie, zmniejsz 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

Wspinaczka po schodach

Wchodzisz po schodach. Aby dotrzeć na szczyt, potrzeba n stopni. Za każdym razem możesz wspiąć się o 1 lub 2 stopnie. Ile różnych sposobów możesz wspiąć się na szczyt? Wyjaśnienie: Jest to klasyczny problem programowania dynamicznego, bardzo podobny do ciągu Fibonacciego. Liczba sposobów dotarcia do stopnia n to suma sposobów dotarcia do stopnia n-1 (przez pokonanie 1 stopnia) i sposobów dotarcia do stopnia n-2 (przez pokonanie 2 stopni). 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

Iloczyn tablicy oprócz samego siebie

Dla danej tablicy liczb całkowitych nums, zwróć tablicę answer taką, że answer[i] jest równa iloczynowi wszystkich elementów nums z wyjątkiem nums[i]. Nie używaj operatora dzielenia. Wyjaśnienie: Możesz rozwiązać ten problem, obliczając iloczyny prefiksów i iloczyny sufiksów. Wynik na indeksie i jest iloczynem wszystkich elementów przed i pomnożonym przez iloczyn wszystkich elementów po 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]

Liczba wysp

Dla danej dwuwymiarowej mapy siatki z '1' (ląd) i '0' (woda), policz liczbę wysp. Wyspa jest otoczona wodą i jest utworzona przez łączenie sąsiadujących lądów poziomo lub pionowo. Wyjaśnienie: Iteruj przez każdą komórkę siatki. Jeśli znajdziesz '1' (ląd), zwiększ licznik wysp i rozpocznij przeszukiwanie w głąb (DFS) lub przeszukiwanie wszerz (BFS) z tej komórki. Podczas przeszukiwania oznacz wszystkie połączone '1' jako '0' (lub odwiedzone), aby uniknąć ponownego ich liczenia.

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)

Pamięć podręczna LRU

Zaprojektuj i zaimplementuj strukturę danych dla pamięci podręcznej typu Least Recently Used (LRU). Powinna obsługiwać operacje get i put. Wyjaśnienie: Pamięć podręczna LRU usuwa najmniej ostatnio używany element, gdy pojemność zostanie osiągnięta. Powszechna implementacja wykorzystuje Map (lub mapę hash) dla wyszukiwania w czasie O(1) i dwukrotnie połączoną listę dla aktualizacji statusu 'ostatnio używane' w czasie O(1). Mapa JavaScript zachowuje kolejność wstawiania, co może uprościć podstawową implementację.

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

Generowanie nawiasów

Dla danych n par nawiasów, napisz funkcję, która wygeneruje wszystkie kombinacje poprawnie sformułowanych nawiasów. Wyjaśnienie: Jest to klasyczny problem przeszukiwania z nawrotami. Utrzymuj liczniki dla nawiasów otwierających i zamykających. Możesz dodać nawias otwierający, jeśli open < n. Możesz dodać nawias zamykający, jeśli close < open. Przypadek podstawowy to, gdy długość ciągu osiągnie 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)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Pojemnik z największą ilością wody

Danych jest n nieujemnych liczb całkowitych a1, a2, ..., an, gdzie każda reprezentuje punkt o współrzędnej (i, ai). Rysowane są n pionowych linii tak, że dwa punkty końcowe linii i znajdują się w (i, ai) i (i, 0). Znajdź dwie linie, które razem z osią x tworzą pojemnik, tak aby pojemnik zawierał najwięcej wody. Wyjaśnienie: Użyj podejścia dwóch wskaźników. Zacznij z jednym wskaźnikiem na początku i jednym na końcu. Oblicz pole. Pole jest ograniczone przez krótszą linię. Aby potencjalnie znaleźć większe pole, przesuń wskaźnik wskazujący na krótszą linię do środka.

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

Dla danej tablicy nums zawierającej n liczb całkowitych, czy istnieją elementy a, b, c w nums takie, że a + b + c = 0? Znajdź wszystkie unikalne trójki w tablicy, które sumują się do zera. Wyjaśnienie: Najpierw posortuj tablicę. Następnie, iteruj przez tablicę za pomocą jednego wskaźnika i. Dla każdego i, użyj dwóch kolejnych wskaźników, left (rozpoczynającego się od i+1) i right (rozpoczynającego się od końca), aby znaleźć dwie liczby, które sumują się do -nums[i]. Obsłuż duplikaty, aby zapewnić unikalne trójki.

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

Pozycja wstawienia wyszukiwania

Dla danej posortowanej tablicy unikalnych liczb całkowitych i wartości docelowej, zwróć indeks, jeśli cel zostanie znaleziony. Jeśli nie, zwróć indeks, w którym powinien zostać wstawiony w kolejności. Wyjaśnienie: Jest to wariant wyszukiwania binarnego. Wykonaj wyszukiwanie binarne, ale jeśli element nie zostanie znaleziony, wskaźnik start znajdzie się w pozycji, w której element powinien zostać wstawiony.

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

Scalanie dwóch posortowanych list (listy połączone)

Scal dwie posortowane listy połączone i zwróć je jako nową posortowaną listę. Nowa lista powinna być utworzona przez połączenie węzłów dwóch pierwszych list. Wyjaśnienie: Użyj fikcyjnego węzła nagłówka, aby uprościć proces. Użyj wskaźnika do budowania nowej listy. Porównaj bieżące węzły obu list wejściowych i dołącz mniejszy do nowej listy, przesuwając wskaźnik tej listy. Powtarzaj, aż jedna lista zostanie wyczerpana, a następnie dołącz resztę drugiej listy.

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

Drzewo symetryczne

Dla danego drzewa binarnego sprawdź, czy jest ono swoim lustrzanym odbiciem (tj. symetryczne względem swojego środka). Wyjaśnienie: Można to rozwiązać rekurencyjnie. Drzewo jest symetryczne, jeśli lewe poddrzewo jest lustrzanym odbiciem prawego poddrzewa. Utwórz funkcję pomocniczą isMirror(t1, t2), która sprawdza, czy dwa drzewa są lustrzane. Powinna ona sprawdzać: 1. t1.val === t2.val. 2. t1.left jest lustrzanym odbiciem t2.right. 3. t1.right jest lustrzanym odbiciem 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.

Przejście poziomowe (BST/Drzewo)

Dla danego drzewa binarnego zwróć jego przejście poziomowe wartości węzłów. (tzn. od lewej do prawej, poziom po poziomie). Wyjaśnienie: Jest to przeszukiwanie wszerz (BFS). Użyj kolejki. Zacznij od dodania korzenia do kolejki. Dopóki kolejka nie jest pusta, przetwarzaj wszystkie węzły na bieżącym poziomie. Dla każdego przetworzonego węzła dodaj jego dzieci (jeśli istnieją) do kolejki na następny poziom.

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

Konwersja posortowanej tablicy na zrównoważone wysokościowo BST

Dla danej tablicy, której elementy są posortowane rosnąco, przekształć ją w zrównoważone wysokościowo binarne drzewo poszukiwań (BST). Wyjaśnienie: Aby utworzyć BST zrównoważone wysokościowo, należy wybrać środkowy element tablicy jako korzeń. Następnie rekurencyjnie zbudować lewe poddrzewo z lewej połowy tablicy i prawe poddrzewo z prawej połowy.

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]

Implementacja bind

Zaimplementuj własną wersję Function.prototype.bind. Wyjaśnienie: bind tworzy nową funkcję, która po wywołaniu ma słowo kluczowe this ustawione na podaną wartość, z podaną sekwencją argumentów poprzedzającą wszelkie argumenty podane podczas wywoływania nowej funkcji. Użyj apply lub call wewnątrz zwracanej funkcji, obsługując częściowe zastosowanie (wstępnie ustawione argumenty).

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

Znajdź K-ty największy element

Znajdź k-ty największy element w nieposortowanej tablicy. Zauważ, że jest to k-ty największy element w posortowanej kolejności, a nie k-ty unikalny element. Wyjaśnienie: Prostym podejściem jest posortowanie tablicy, a następnie wybranie elementu na indeksie n - k. Bardziej efektywne podejście dla dużych tablic obejmuje użycie min-kopy lub algorytmu selekcji, takiego jak 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

Sprawdź, czy obiekt ma właściwość

Jak sprawdzić, czy obiekt ma określoną właściwość? Wyjaśnienie: Możesz użyć operatora in (sprawdza własne i dziedziczone właściwości), Object.prototype.hasOwnProperty.call(obj, prop) (sprawdza tylko własne właściwości) lub po prostu obj.prop !== undefined (może być trudne z wartościami 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)

Liczba całkowita na rzymską

Napisz funkcję, która przekształca liczbę całkowitą na jej rzymską reprezentację. Wyjaśnienie: Utwórz mapowanie cyfr rzymskich i odpowiadających im wartości, uporządkowanych od największej do najmniejszej. Iteruj przez tę mapę. Dla każdej wartości odejmij ją od liczby wejściowej tyle razy, ile to możliwe, za każdym razem dodając cyfrę rzymską.

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

Rzymska na całkowitą

Napisz funkcję, która przekształca cyfrę rzymską na liczbę całkowitą. Wyjaśnienie: Utwórz mapę symboli rzymskich na wartości. Iteruj przez ciąg. Jeśli wartość bieżącego symbolu jest mniejsza niż wartość następnego symbolu (jak IV lub IX), odejmij bieżącą wartość; w przeciwnym razie, dodaj ją.

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

Implementacja zbioru (Set)

Zaimplementuj podstawową strukturę danych Set (bez użycia wbudowanego Set) z metodami add, has, delete i size. Wyjaśnienie: Możesz użyć obiektu JavaScript (mapy hash) jako podstawowego miejsca przechowywania. Klucze będą elementami zbioru (może być konieczne obsłużenie sposobu przechowywania różnych typów i zapewnienie unikalności jako kluczy).

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

Trójkąt Pascala

Dla danej liczby całkowitej numRows, wygeneruj pierwsze numRows wierszy trójkąta Pascala. Wyjaśnienie: W trójkącie Pascala każda liczba jest sumą dwóch liczb bezpośrednio nad nią. Zacznij od [[1]]. Dla każdego kolejnego wiersza zacznij i zakończ na 1. Każdy środkowy element jest sumą elementu o tym samym indeksie i poprzedniego indeksu z wiersza powyżej.

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

Wyszukiwanie słów

Dla danej dwuwymiarowej tablicy (planszy) i słowa, sprawdź, czy słowo istnieje w siatce. Słowo może być zbudowane z liter kolejno sąsiadujących komórek, gdzie komórki 'sąsiadujące' sąsiadują ze sobą poziomo lub pionowo. Wyjaśnienie: Wymaga to przeszukiwania w głąb (DFS) z nawrotami. Iteruj przez każdą komórkę. Jeśli komórka pasuje do pierwszej litery słowa, rozpocznij DFS. Funkcja DFS sprawdza sąsiadów, upewniając się, że pasują do następnej litery i nie zostały odwiedzone w bieżącej ścieżce. Jeśli ścieżka się nie powiedzie, wróć, odznaczając komórkę.

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

Minimalny podciąg okienkowy

Dla dwóch danych ciągów znaków s i t, znajdź minimalne okno w s, które będzie zawierało wszystkie znaki z t. Jeśli nie ma takiego okna w s, które obejmuje wszystkie znaki w t, zwróć pusty ciąg ''. Wyjaśnienie: Użyj podejścia ruchomego okna z dwoma wskaźnikami (left i right) i mapami hash. Jedna mapa przechowuje potrzebne zliczenia znaków z t. Inna mapa przechowuje zliczenia w bieżącym oknie. Rozszerzaj okno za pomocą right. Gdy okno zawiera wszystkie potrzebne znaki, spróbuj je zmniejszyć za pomocą left, aby znaleźć minimalne możliwe okno.

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'

Odwróć listę połączoną

Dla danej głowy listy połączonej jednokierunkowej, odwróć listę i zwróć odwróconą listę. Wyjaśnienie: Musisz iterować przez listę, zmieniając wskaźnik next każdego węzła, aby wskazywał na poprzedni węzeł. Śledź węzły previous, current i next podczas iteracji.

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

Wykryj cykl w liście połączonej

Dla danej głowy, głowy listy połączonej, określ, czy lista połączona ma w sobie cykl. Wyjaśnienie: Użyj algorytmu 'Żółwia i Zająca' Floyda. Miej dwa wskaźniki, jeden poruszający się o jeden krok na raz (slow) i jeden poruszający się o dwa kroki na raz (fast). Jeśli istnieje cykl, wskaźnik fast w końcu dogoni wskaźnik 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.

Implementacja Object.create

Zaimplementuj funkcję, która naśladuje zachowanie Object.create(proto). Wyjaśnienie: Object.create tworzy nowy obiekt, używając istniejącego obiektu jako prototypu nowo utworzonego obiektu. Możesz to osiągnąć, tworząc tymczasową funkcję konstruktora, ustawiając jej prototyp na podany proto, a następnie zwracając nową instancję.

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

Co to jest Hoisting?

Wyjaśnij hoisting w JavaScript i podaj przykład. Wyjaśnienie: Hoisting to domyślne zachowanie JavaScript polegające na przenoszeniu deklaracji na szczyt bieżącego zasięgu (globalnego lub funkcji) przed wykonaniem kodu. Deklaracje zmiennych (var) są przenoszone i inicjowane wartością undefined. Deklaracje funkcji są w pełni przenoszone (zarówno nazwa, jak i ciało). let i const są przenoszone, ale nie inicjowane, co prowadzi do Tymczasowej Martwej Strefy (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!'); }

Wyjaśnienie propagacji zdarzeń (Event Bubbling i Capturing)

Wyjaśnij propagację zdarzeń (Event Bubbling i Capturing) w kontekście DOM. Wyjaśnienie: Są to dwie fazy propagacji zdarzeń w HTML DOM. Faza przechwytywania (Capturing Phase): Zdarzenie przemieszcza się w dół od window do elementu docelowego. Faza bąbelkowania (Bubbling Phase): Zdarzenie przemieszcza się w górę od elementu docelowego z powrotem do window. Domyślnie większość obsługi zdarzeń jest rejestrowana podczas fazy bąbelkowania. Możesz użyć addEventListener(type, listener, useCapture) z useCapture = true, aby obsługiwać zdarzenia podczas fazy przechwytywania.

// 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 */

Ręczna implementacja JSON.parse (podstawowa)

Spróbuj zaimplementować bardzo podstawową wersję JSON.parse (obsługa prostych obiektów, tablic, ciągów znaków, liczb). Wyjaśnienie: W pełni jest to bardzo złożone zadanie, ale w warunkach kodowania na żywo możesz zostać poproszony o obsługę bardzo uproszczonego podzbioru. Będziesz musiał przeanalizować ciąg znaków, identyfikując granice obiektów {} i tablic [], pary klucz-wartość (key: value) i podstawowe typy danych. eval lub new Function mogą oszukać, ale są niebezpieczne. Prawdziwy parser używałby leksyka/tokenizera i parsera.

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

Spłaszczanie głęboko zagnieżdżonej tablicy

Napisz funkcję, która spłaszcza głęboko zagnieżdżoną tablicę. Wyjaśnienie: W przeciwieństwie do poprzedniego spłaszczania (jeden poziom), to musi obsługiwać dowolny poziom zagnieżdżenia. Rekurencja jest naturalnym dopasowaniem. Iteruj przez tablicę. Jeśli element jest tablicą, rekurencyjnie wywołaj na nim spłaszczanie i połącz wynik. W przeciwnym razie dodaj element.

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]

Implementacja drzewa prefiksowego (Trie)

Zaimplementuj strukturę danych Trie z metodami insert, search i startsWith. Wyjaśnienie: Trie to struktura danych przypominająca drzewo, używana do efektywnego przechowywania i pobierania kluczy w zbiorze danych ciągów znaków. Każdy węzeł reprezentuje znak, a ścieżki od korzenia reprezentują słowa lub prefiksy.

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

Tasowanie tablicy (Fisher-Yates)

Napisz funkcję, która tasuje tablicę w miejscu, używając algorytmu Fisher-Yates (lub Knuth). Wyjaśnienie: Algorytm Fisher-Yates iteruje tablicę od ostatniego elementu do pierwszego. W każdej iteracji zamienia bieżący element z losowo wybranym elementem z początku tablicy do bieżącego elementu (włącznie).

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]

Kompozycja funkcji

Zaimplementuj funkcję compose, która przyjmuje wiele funkcji i zwraca nową funkcję, która stosuje je od prawej do lewej. Wyjaśnienie: Kompozycja funkcji (f ∘ g)(x) = f(g(x)) stosuje jedną funkcję do wyniku innej. Ogólna funkcja compose(f, g, h) oznaczałaby f(g(h(x))). Użyj reduceRight dla eleganckiej implementacji.

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

Pipe Functions

Zaimplementuj funkcję pipe, która przyjmuje wiele funkcji i zwraca nową funkcję, która stosuje je od lewej do prawej. Wyjaśnienie: Podobnie jak compose, ale kolejność aplikacji jest odwrócona: pipe(f, g, h) oznacza h(g(f(x))). Użyj reduce do implementacji.

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

Problem wydawania reszty (Coin Change)

Dla danej tablicy nominałów monet i kwoty, znajdź minimalną liczbę monet potrzebnych do uzyskania tej kwoty. Zakładamy nieskończoną podaż każdej monety. Wyjaśnienie: Jest to klasyczny problem programowania dynamicznego. Utwórz tablicę dp, gdzie dp[i] przechowuje minimalną liczbę monet potrzebnych dla kwoty i. dp[i] = min(dp[i - moneta]) + 1 dla wszystkich monet.

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

Najniższy wspólny przodek (BST)

Dla danego binarnego drzewa poszukiwań (BST), znajdź najniższego wspólnego przodka (LCA) dwóch danych węzłów w BST. Wyjaśnienie: LCA to najgłębszy węzeł, który ma oba dane węzły jako potomków. W BST można go znaleźć, przechodząc od korzenia. Jeśli oba węzły są mniejsze niż bieżący węzeł, idź w lewo. Jeśli oba są większe, idź w prawo. Jeśli jeden jest mniejszy, a drugi większy (lub jeden jest bieżącym węzłem), to bieżący węzeł jest 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

Serializacja i deserializacja drzewa binarnego

Zaprojektuj algorytm do serializacji i deserializacji drzewa binarnego. Wyjaśnienie: Serializacja konwertuje drzewo na ciąg znaków lub tablicę. Deserializacja rekonstruuje drzewo. Powszechnym sposobem jest przejście pre-order (DFS). Użyj specjalnego znacznika (np. '#') dla węzłów null, aby zachować strukturę.

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,#,#'

Implementacja setInterval za pomocą setTimeout

Zaimplementuj funkcję mySetInterval, która naśladuje setInterval, ale używa setTimeout rekurencyjnie. Wyjaśnienie: setInterval uruchamia funkcję wielokrotnie co N milisekund. Możesz to osiągnąć, sprawiając, że funkcja wywoła się sama za pomocą setTimeout po każdym wykonaniu. Musisz także mieć sposób na jej wyczyszczenie (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);

Przechodzenie grafu (BFS i DFS)

Zaimplementuj przeszukiwanie wszerz (BFS) i przeszukiwanie w głąb (DFS) dla danego grafu (reprezentacja listy sąsiedztwa). Wyjaśnienie: BFS najpierw bada sąsiadów (używając kolejki), podczas gdy DFS bada jak najdalej wzdłuż każdej gałęzi (używając stosu lub rekurencji). Śledź odwiedzone węzły, aby uniknąć cykli i zbędnej pracy.

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

Obracanie obrazu (macierz)

Masz daną dwuwymiarową macierz n x n reprezentującą obraz. Obróć obraz o 90 stopni (zgodnie z ruchem wskazówek zegara) w miejscu. Wyjaśnienie: Powszechnym sposobem osiągnięcia tego jest najpierw transpozycja macierzy (zamiana matrix[i][j] z matrix[j][i]), a następnie odwrócenie każdego wiersza.

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

Przechodzenie macierzy spiralnej

Biorąc pod uwagę macierz m x n, zwróć wszystkie elementy macierzy w kolejności spiralnej.

Wyjaśnienie: Użyj czterech wskaźników do zdefiniowania granic: top, bottom, left, right. Przejdź górny wiersz, następnie prawą kolumnę, następnie dolny wiersz, następnie lewą kolumnę, zmniejszając granice po każdym przejściu, aż left przekroczy right lub top przekroczy 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]

Zerowanie macierzy

Biorąc pod uwagę macierz m x n, jeśli element jest równy 0, ustaw cały jego wiersz i kolumnę na 0. Wykonaj to w miejscu.

Wyjaśnienie: Naiwne podejście wymaga dodatkowej pamięci O(m*n). Aby to zrobić w przestrzeni O(1) (lub O(m+n)), możesz użyć pierwszego wiersza i pierwszej kolumny do przechowywania informacji o tym, które wiersze/kolumny należy wyzerować. Potrzebujesz osobnych flag do określenia, czy sam pierwszy wiersz/kolumna wymaga wyzerowania.

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

Implementacja Promise.race

Zaimplementuj funkcję, która zachowuje się jak Promise.race.

Wyjaśnienie: Promise.race przyjmuje tablicę obietnic i zwraca pojedynczą obietnicę. Ta zwrócona obietnica rozstrzyga się (rozwiązuje lub odrzuca) tak szybko, jak tylko któraś z obietnic wejściowych się rozstrzygnie, z wartością lub powodem tej obietnicy.

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'

Wzorzec Singleton

Zaimplementuj wzorzec projektowy Singleton w JavaScript.

Wyjaśnienie: Wzorzec Singleton zapewnia, że klasa ma tylko jedną instancję i zapewnia do niej globalny punkt dostępu. Można to osiągnąć za pomocą domknięcia do przechowywania instancji.

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'

Walidacja adresu IP

Napisz funkcję sprawdzającą, czy dany ciąg znaków jest prawidłowym adresem IPv4 lub IPv6.

Wyjaśnienie: Dla IPv4 sprawdź 4 liczby (0-255) oddzielone kropkami, bez wiodących zer (z wyjątkiem samego '0'). Dla IPv6 sprawdź 8 grup 1-4 cyfr szesnastkowych oddzielonych dwukropkami (obsłuż wariacje takie jak '::'). Można użyć wyrażeń regularnych, ale ręczne parsowanie jest często jaśniejsze do celów rozmów kwalifikacyjnych.

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

Znajdź element szczytowy

Element szczytowy to element, który jest ściśle większy od swoich sąsiadów. Biorąc pod uwagę tablicę wejściową nums, znajdź element szczytowy i zwróć jego indeks.

Wyjaśnienie: Ponieważ każdy szczyt będzie działał, a nums[-1] i nums[n] są uważane za -Infinity, możesz użyć zmodyfikowanego wyszukiwania binarnego. Jeśli nums[mid] jest mniejsze niż nums[mid+1], szczyt musi istnieć po prawej stronie. W przeciwnym razie szczyt musi istnieć po lewej stronie (lub sam mid jest szczytem).

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)

Zliczanie bitów

Biorąc pod uwagę liczbę całkowitą n, zwróć tablicę ans o długości n + 1, taką, że dla każdego i (0 <= i <= n), ans[i] jest liczbą jedynek w binarnej reprezentacji i.

Wyjaśnienie: Możesz to rozwiązać za pomocą programowania dynamicznego. Zauważ zależność: dp[i] = dp[i >> 1] + (i & 1). Liczba jedynek w i to liczba jedynek w i przesuniętych w prawo (tj. i/2), plus 1, jeśli i jest nieparzyste.

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]

Potęga dwójki

Biorąc pod uwagę liczbę całkowitą n, zwróć true, jeśli jest to potęga dwójki. W przeciwnym razie zwróć false.

Wyjaśnienie: Potęga dwójki w reprezentacji binarnej ma dokładnie jeden bit '1' (np. 1=1, 2=10, 4=100, 8=1000). Sprytna sztuczka bitowa polega na sprawdzeniu, czy n > 0 i (n & (n - 1)) === 0. Jeśli n jest potęgą dwójki, n-1 będzie miało wszystkie bity poniżej tego '1' ustawione na '1'. Wynik operacji ANDing będzie równy 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

Scalanie przedziałów

Biorąc pod uwagę tablicę przedziałów intervals, gdzie intervals[i] = [starti, endi], scal wszystkie nakładające się przedziały i zwróć tablicę przedziałów nienakładających się.

Wyjaśnienie: Najpierw posortuj przedziały według ich czasów początkowych. Następnie iteruj po posortowanych przedziałach. Jeśli bieżący przedział nakłada się na ostatni przedział na liście wyników, scal je, aktualizując czas końcowy ostatniego przedziału. W przeciwnym razie dodaj bieżący przedział do listy wyników.

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

Podział słowa

Biorąc pod uwagę ciąg znaków s i słownik ciągów znaków wordDict, zwróć true, jeśli s może być podzielone na sekwencję jednego lub więcej słów słownika, oddzielonych spacjami.

Wyjaśnienie: Użyj programowania dynamicznego. Utwórz tablicę boolean dp, gdzie dp[i] jest true, jeśli podciąg s[0...i-1] może być podzielony. dp[i] jest true, jeśli istnieje j < i takie, że dp[j] jest true, a podciąg s[j...i-1] znajduje się w słowniku.

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

Implementacja Array.prototype.flat

Zaimplementuj własną wersję Array.prototype.flat, która tworzy nową tablicę ze wszystkimi elementami podtablic połączonymi rekurencyjnie do określonej głębokości.

Wyjaśnienie: Użyj rekurencji. Iteruj po tablicy. Jeśli element jest tablicą i bieżąca głębokość jest mniejsza niż określona głębokość, rekurencyjnie wywołaj funkcję flatten na nim. W przeciwnym razie dodaj element. Obsłuż domyślną głębokość równą 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]

Odwróć słowa w ciągu znaków

Biorąc pod uwagę ciąg wejściowy s, odwróć kolejność słów.

Wyjaśnienie: 'Słowo' jest zdefiniowane jako sekwencja znaków innych niż spacje. Usuń białe znaki z początku i końca ciągu, podziel go za pomocą jednej lub więcej spacji, odfiltruj puste ciągi znaków powstałe w wyniku wielu spacji, odwróć tablicę i ponownie połącz ją pojedynczymi spacjami.

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 ciągu zapytania

Napisz funkcję do parsowania ciągu zapytania URL na obiekt.

Wyjaśnienie: Podziel ciąg znaków za pomocą znaku &. Dla każdej części podziel za pomocą znaku = aby uzyskać klucz i wartość. Dekoduj komponenty URI zarówno dla klucza, jak i wartości. Obsłuż przypadki, w których klucz może pojawić się wiele razy (utwórz tablicę) lub nie ma wartości.

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 }

Użycie Proxy do walidacji

Zaprezentuj, jak użyć obiektu Proxy do walidacji właściwości obiektu.

Wyjaśnienie: Proxy pozwala na przechwytywanie i dostosowywanie operacji wykonywanych na obiekcie. Użyj pułapki set do walidacji wartości przed przypisaniem ich do właściwości. Wyrzuć błąd, jeśli walidacja się nie powiedzie.

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

Pokoje spotkań

Biorąc pod uwagę tablicę przedziałów czasu spotkań [[start1, end1], [start2, end2], ...], określ, czy osoba mogłaby uczestniczyć we wszystkich spotkaniach.

Wyjaśnienie: Jeśli osoba może uczestniczyć we wszystkich spotkaniach, oznacza to, że żadne dwa spotkania się nie nakładają. Aby to sprawdzić, posortuj przedziały według ich czasów początkowych. Następnie iteruj po posortowanych przedziałach i sprawdź, czy czas początkowy bieżącego spotkania jest wcześniejszy niż czas zakończenia poprzedniego spotkania. Jeśli tak, następuje nakładanie się, a osoba nie może uczestniczyć we wszystkich spotkaniach.

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

Implementacja Promise.any

Zaimplementuj funkcję, która zachowuje się jak Promise.any.

Wyjaśnienie: Promise.any przyjmuje tablicę obietnic i zwraca pojedynczą obietnicę. Ta obietnica spełnia się, gdy tylko któraś z obietnic wejściowych się spełni. Jeśli wszystkie obietnice wejściowe zostaną odrzucone, odrzuca z AggregateError zawierającym wszystkie powody odrzucenia.

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

Wzorzec Obserwator

Zaimplementuj wzorzec projektowy Obserwator (Pub/Sub) w JavaScript.

Wyjaśnienie: Wzorzec Obserwator definiuje zależność jeden do wielu między obiektami. Gdy jeden obiekt (Podmiot) zmienia stan, wszyscy jego zależni (Obserwatorzy) są automatycznie powiadamiani i aktualizowani. Podmiot utrzymuje listę obserwatorów i udostępnia metody do ich dodawania, usuwania i powiadamiania.

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!

Znajdź powtarzający się numer

Biorąc pod uwagę tablicę liczb całkowitych nums zawierającą n + 1 liczb całkowitych, gdzie każda liczba całkowita jest w zakresie [1, n] włącznie, znajdź jedną powtarzającą się liczbę.

Wyjaśnienie: Ponieważ liczby są w zakresie [1, n], a rozmiar tablicy wynosi n+1, gwarantuje to co najmniej jeden duplikat. Można to zmapować na problem 'Znajdź cykl w liście połączonej' (Żółw i Zając Floyda). Traktuj wartości tablicy jako wskaźniki: 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

Podstawowy sanitizer HTML

Napisz podstawową funkcję do czyszczenia ciągu HTML, usuwając potencjalnie szkodliwe tagi (takie jak script) przy zachowaniu bezpiecznych tagów (takich jak p, b).

Wyjaśnienie: To jest złożony temat, a rzeczywiste sanitizery są wyrafinowane. W przypadku rozmowy kwalifikacyjnej podstawowe podejście może obejmować użycie wyrażeń regularnych do usuwania określonych tagów lub zezwalanie tylko na białą listę tagów. To nie jest bezpieczne do użytku produkcyjnego.

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>

Odległość edycyjna

Biorąc pod uwagę dwa ciągi znaków word1 i word2, zwróć minimalną liczbę operacji (wstawianie, usuwanie lub zamiana) wymaganych do przekształcenia word1 w word2.

Wyjaśnienie: Jest to klasyczny problem programowania dynamicznego (odległość Levenshteina). Utwórz dwuwymiarową tablicę dp, gdzie dp[i][j] jest odległością edycyjną między pierwszymi i znakami word1 a pierwszymi j znakami word2. Relacja rekurencyjna obejmuje uwzględnienie kosztu wstawienia, usunięcia i zamiany.

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

Najdłuższy podciąg rosnący (LIS)

Biorąc pod uwagę tablicę liczb całkowitych nums, zwróć długość najdłuższego ściśle rosnącego podciągu.

Wyjaśnienie: Użyj programowania dynamicznego. Niech dp[i] będzie długością LIS kończącego się na indeksie i. Aby obliczyć dp[i], znajdź wszystkie j < i takie, że nums[j] < nums[i], i weź dp[i] = 1 + max(dp[j]). Ostateczna odpowiedź to maksymalna wartość w tablicy 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)

Problem N-Hetmanów

Zagadka N-Hetmanów to problem umieszczenia N hetmanów szachowych na szachownicy N×N tak, aby żadne dwa hetmany się nie szachowały. Biorąc pod uwagę N, zwróć jedno prawidłowe rozwiązanie (lub wszystkie).

Wyjaśnienie: Użyj backtrackingu. Umieszczaj hetmany wiersz po wierszu. Dla każdego wiersza spróbuj umieścić hetmana w każdej kolumnie. Przed umieszczeniem sprawdź, czy proponowana pozycja jest bezpieczna (nie atakowana przez hetmany w poprzednich wierszach). Jeśli bezpieczna, umieść ją i rekurencyjnie wywołaj dla następnego wiersza. Jeśli rekurencja się nie powiedzie, cofnij się (usuń hetmana) i spróbuj następnej kolumny.

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

Użycie WeakMap do danych prywatnych

Zaprezentuj, jak użyć WeakMap do przechowywania prywatnych danych dla instancji klas.

Wyjaśnienie: WeakMap pozwala na powiązanie danych z obiektem w sposób, który nie zapobiega garbage collection, jeśli obiekt nie jest już referencjonowany. Jest to przydatne do tworzenia 'prywatnych' członków w klasach JavaScript przed powszechnym udostępnieniem natywnych pól prywatnych lub do specyficznych przypadków użycia.

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

Implementacja Promise.allSettled

Zaimplementuj funkcję, która zachowuje się jak Promise.allSettled.

Wyjaśnienie: Promise.allSettled przyjmuje tablicę obietnic i zwraca pojedynczą obietnicę. Ta obietnica spełnia się, gdy wszystkie obietnice wejściowe zostały rozstrzygnięte (albo spełnione, albo odrzucone). Wartością spełnienia jest tablica obiektów, z których każdy opisuje wynik jednej obietnicy ({status: 'fulfilled', value: ...} lub {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'}]

Znajdź medianę dwóch posortowanych tablic

Biorąc pod uwagę dwie posortowane tablice nums1 i nums2 o rozmiarach odpowiednio m i n, zwróć medianę dwóch posortowanych tablic.

Wyjaśnienie: Jest to trudny problem często rozwiązywany za pomocą podejścia wyszukiwania binarnego na mniejszej tablicy. Celem jest podzielenie obu tablic w taki sposób, aby wszystkie elementy po lewej stronie były mniejsze lub równe wszystkim elementom po prawej stronie, a liczba elementów po lewej była równa (lub o jeden większa niż) po prawej. Medianę można następnie obliczyć z elementów granicznych.

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

Rozwiązywanie Sudoku

Napisz program do rozwiązywania łamigłówki Sudoku poprzez wypełnianie pustych komórek.

Wyjaśnienie: Użyj backtrackingu. Znajdź pustą komórkę. Spróbuj wypełnić ją liczbami od 1 do 9. Dla każdej liczby sprawdź, czy jest prawidłowa (nie narusza zasad Sudoku). Jeśli prawidłowa, rekurencyjnie wywołaj solver. Jeśli rekurencja zwróci true, znaleziono rozwiązanie. Jeśli nie, cofnij się (zresetuj komórkę) i spróbuj następnej liczby.

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.

Implementacja podstawowego wzorca Middleware

Zaimplementuj prosty wzorzec middleware często spotykany w frameworkach webowych.

Wyjaśnienie: Funkcje middleware przetwarzają żądanie zanim dotrze ono do końcowego handlera. Każdy middleware może modyfikować żądanie/odpowiedź lub przekazywać kontrolę do następnego middleware. Utwórz klasę lub obiekt, który zarządza listą funkcji middleware i wykonuje je sekwencyjnie.

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 }

Wykryj cykl w grafie skierowanym

Biorąc pod uwagę graf skierowany, napisz funkcję, aby określić, czy zawiera cykl.

Wyjaśnienie: Użyj przeszukiwania w głąb (DFS). Zachowaj dwa zbiory: visiting (węzły aktualnie na stosie rekurencji) i visited (węzły, które zostały w pełni zbadane). Jeśli napotkasz węzeł w zbiorze visiting podczas DFS, znalazłeś krawędź wsteczną, co oznacza, że istnieje cykl.

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

Implementacja zachowania Object.freeze

Wyjaśnij Object.freeze i zaimplementuj (płytką) wersję.

Wyjaśnienie: Object.freeze zapobiega dodawaniu nowych właściwości, usuwaniu istniejących właściwości oraz zmienianiu istniejących właściwości lub ich wyliczalności, konfigurowalności lub zapisywalności. Sprawia, że obiekt jest niemutowalny (płytko). Możesz to zaimplementować, używając Object.preventExtensions i Object.defineProperty, aby istniejące właściwości były niezapisywalne i niekonfigurowalne.

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 }

Użycie requestAnimationFrame do płynnej animacji

Wyjaśnij requestAnimationFrame i pokaż, jak go używać do prostej pętli animacji.

Wyjaśnienie: requestAnimationFrame (rAF) informuje przeglądarkę, że chcesz wykonać animację i prosi przeglądarkę o wywołanie określonej funkcji w celu zaktualizowania animacji przed następnym odświeżeniem. Jest to bardziej wydajne i płynniejsze niż użycie setTimeout lub setInterval do animacji, ponieważ synchronizuje się z częstotliwością odświeżania przeglądarki i pauzuje, gdy karta nie jest widoczna.

// 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.');

Implementacja Array.prototype.some

Zaimplementuj własną wersję Array.prototype.some.

Wyjaśnienie: some testuje, czy co najmniej jeden element w tablicy spełnia test zaimplementowany przez dostarczoną funkcję. Zwraca true, jeśli znajdzie element, dla którego funkcja zwrotna zwraca true; w przeciwnym razie zwraca 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

Implementacja Array.prototype.every

Zaimplementuj własną wersję Array.prototype.every.

Wyjaśnienie: every testuje, czy wszystkie elementy w tablicy spełniają test zaimplementowany przez dostarczoną funkcję. Zwraca true, jeśli wszystkie elementy spełniają (lub jeśli tablica jest pusta), i false, jeśli którykolwiek element nie spełnia.

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

Implementacja Array.prototype.findIndex

Zaimplementuj własną wersję Array.prototype.findIndex.

Wyjaśnienie: findIndex zwraca indeks pierwszego elementu w tablicy, który spełnia dostarczoną funkcję testującą. W przeciwnym razie zwraca -1, wskazując, że żaden element nie przeszedł testu.

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

Czym są Web Workers?

Wyjaśnij, czym są Web Workers i ich główne zastosowanie.

Wyjaśnienie: Web Workers to sposób, w jaki zawartość sieciowa może uruchamiać skrypty w wątkach tła. Głównym zastosowaniem jest odciążenie długotrwałych lub wymagających obliczeniowo zadań z głównego wątku (który obsługuje aktualizacje interfejsu użytkownika i interakcje z użytkownikiem), zapobiegając w ten sposób nieodpowiedzialności interfejsu użytkownika lub 'zawieszaniu się'. Workers komunikują się z głównym wątkiem za pośrednictwem przesyłania wiadomości (postMessage i 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.');

Sposoby dekodowania

Wiadomość zawierająca litery od A-Z może być zakodowana w liczby za pomocą mapowania A -> 1, B -> 2, ..., Z -> 26. Biorąc pod uwagę ciąg znaków s zawierający tylko cyfry, zwróć liczbę sposobów dekodowania go.

Wyjaśnienie: Użyj programowania dynamicznego. Niech dp[i] będzie liczbą sposobów dekodowania s[0...i-1]. dp[i] zależy od dp[i-1] (jeśli s[i-1] jest prawidłowym kodem 1-cyfrowym) i dp[i-2] (jeśli s[i-2...i-1] jest prawidłowym kodem 2-cyfrowym).

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

Dodawanie bitowe (bez +)

Napisz funkcję dodającą dwie liczby całkowite bez użycia operatora +.

Wyjaśnienie: Użyj operatorów bitowych. Sumę można obliczyć jako a ^ b (suma bez przeniesienia), a przeniesienie można obliczyć jako (a & b) << 1. Powtarzaj ten proces, aż przeniesienie stanie się 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

Sprawdź, czy liczba jest palindromem

Biorąc pod uwagę liczbę całkowitą x, zwróć true, jeśli x jest palindromem, a false w przeciwnym razie.

Wyjaśnienie: Liczba ujemna nie jest palindromem. Przekształć liczbę na ciąg znaków i sprawdź, czy ciąg jest palindromem (czyta się tak samo do przodu i do tyłu). Alternatywnie, odwróć liczbę matematycznie i porównaj.

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

Wzorzec fabryczny

Zaimplementuj wzorzec projektowy Factory w JavaScript.

Wyjaśnienie: Wzorzec fabryczny zapewnia interfejs do tworzenia obiektów w klasie nadrzędnej, ale pozwala podklasom zmieniać typ tworzonych obiektów. Jest przydatny do tworzenia obiektów bez określania dokładnej klasy.

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

Wyjaśnij Symbol i przypadek użycia

Wyjaśnij, czym jest Symbol w JavaScript i podaj przypadek użycia, taki jak tworzenie prywatnych właściwości lub unikalnych kluczy obiektów.

Wyjaśnienie: Symbol to prymitywny typ danych wprowadzony w ES6. Jego instancje są unikalne i niezmienne. Są często używane jako klucze do właściwości obiektów, aby uniknąć kolizji nazw między różnymi bibliotekami lub do tworzenia pseudoprywatnych właściwości (chociaż nie są one prawdziwie prywatne, nie są dostępne za pośrednictwem typowej iteracji lub 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)

Konwersja arguments na rzeczywistą tablicę

Jak można przekonwertować obiekt arguments (dostępny w funkcjach niebędących funkcjami strzałkowymi) na rzeczywistą tablicę JavaScript?

Wyjaśnienie: Obiekt arguments wygląda jak tablica, ale nią nie jest; brakuje mu metod takich jak map, filter itp. Można go przekonwertować za pomocą:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (Składnia rozprzestrzeniania ES6)
  3. Array.prototype.slice.call(arguments) (Starszy sposób)
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