Live Coding

Prüfen, ob ein Objekt leer ist

Wie prüft man, ob ein JavaScript-Objekt leer ist?

Erklärung: Ein Objekt ist leer, wenn es keine eigenen aufzählbaren Eigenschaften besitzt. Wir können Object.keys(obj) verwenden, welches ein Array der eigenen aufzählbaren Eigenschaftsnamen eines gegebenen Objekts zurückgibt. Wenn die Länge dieses Arrays 0 ist, ist das Objekt leer.

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

Einen String umkehren

Schreiben Sie eine Funktion, die einen gegebenen String umkehrt.

Erklärung: Der einfachste Weg ist, den String in ein Array von Zeichen umzuwandeln, die integrierte reverse()-Methode für Arrays zu verwenden und dann die Zeichen wieder zu einem String zusammenzufügen.

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

Palindrom-Prüfung

Schreiben Sie eine Funktion, die prüft, ob ein gegebener String ein Palindrom ist.

Erklärung: Ein Palindrom ist ein Wort oder eine Phrase, die sich vorwärts und rückwärts gleich liest. Wir können dies prüfen, indem wir den String umkehren (ignorieren Sie Groß-/Kleinschreibung und nicht-alphanumerische Zeichen für eine robustere Prüfung, aber hier führen wir eine einfache Prüfung durch) und ihn mit dem Original vergleichen.

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

Die größte Zahl in einem Array finden

Schreiben Sie eine Funktion, die die größte Zahl in einem Array von Zahlen findet.

Erklärung: Sie können das Array durchlaufen und die bisher gefundene größte Zahl verfolgen. Alternativ können Sie die Math.max()-Funktion zusammen mit dem Spread-Syntax (...) verwenden, um Array-Elemente als Argumente zu übergeben.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // Oder einen Fehler werfen return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

FizzBuzz

Schreiben Sie ein Programm, das Zahlen von 1 bis n ausgibt. Aber für Vielfache von drei geben Sie 'Fizz' anstelle der Zahl aus und für Vielfache von fünf 'Buzz'. Für Zahlen, die Vielfache von drei und fünf sind, geben Sie 'FizzBuzz' aus.

Erklärung: Dieses klassische Problem testet grundlegende Schleifen- und Bedingungslogik. Sie müssen von 1 bis n iterieren und den Modulo-Operator (%) verwenden, um die Teilbarkeit durch 3 und 5 zu prüfen.

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

Duplikate aus einem Array entfernen

Schreiben Sie eine Funktion, die doppelte Elemente aus einem Array entfernt.

Erklärung: Eine moderne und prägnante Methode, dies zu erreichen, ist die Verwendung eines Set. Sets speichern nur eindeutige Werte. Sie können das Array in ein Set und dann wieder in ein Array umwandeln.

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

Anagramm-Prüfung

Schreiben Sie eine Funktion, die prüft, ob zwei Strings Anagramme voneinander sind.

Erklärung: Anagramme sind Wörter, die durch Umordnen der Buchstaben eines anderen gebildet werden. Um dies zu prüfen, können wir die Strings bereinigen, sortieren und vergleichen. Das Bereinigen umfasst das Entfernen von nicht-alphanumerischen Zeichen und das Umwandeln in eine konsistente Groß-/Kleinschreibung.

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

Fakultät berechnen

Schreiben Sie eine Funktion zur Berechnung der Fakultät einer nicht-negativen ganzen Zahl.

Erklärung: Die Fakultät (n!) einer Zahl ist das Produkt aller positiven ganzen Zahlen kleiner oder gleich n. Wir können dies iterativ berechnen, indem wir die Zahlen von 1 bis n multiplizieren.

function factorial(n) { if (n < 0) return undefined; // Fakultät ist für negative Zahlen nicht definiert if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

Alle Zahlen in einem Array summieren

Schreiben Sie eine Funktion, die die Summe aller Zahlen in einem Array zurückgibt.

Erklärung: Sie können die reduce-Methode verwenden, die ideal zum Akkumulieren eines einzelnen Wertes aus einem Array ist. Sie nimmt eine Callback-Funktion und einen Anfangswert (0 für die Summation) entgegen.

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

Ein verschachteltes Array 'flachklopfen'

Schreiben Sie eine Funktion, die ein verschachteltes Array 'flachklopft'. Der Einfachheit halber wird nur eine Ebene der Verschachtelung angenommen.

Erklärung: Für eine einzelne Ebene der Verschachtelung können Sie Array.prototype.concat() mit dem Spread-Operator oder die flat()-Methode (ES2019) verwenden.

function flattenArray(arr) { // Verwendung von flat() (einfacher, wenn ES2019+ in Ordnung ist) // return arr.flat(); // Verwendung von reduce und concat return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

Vokale in einem String zählen

Schreiben Sie eine Funktion, die die Anzahl der Vokale (a, e, i, o, u) in einem gegebenen String zählt.

Erklärung: Durchlaufen Sie den String (ohne Berücksichtigung der Groß-/Kleinschreibung) und prüfen Sie, ob jedes Zeichen ein Vokal ist. Führen Sie einen Zähler.

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

Satz in Titel-Großschreibung umwandeln

Schreiben Sie eine Funktion, die einen Satz in Titel-Großschreibung umwandelt (der erste Buchstabe jedes Wortes wird großgeschrieben).

Erklärung: Teilen Sie den Satz in Wörter auf, durchlaufen Sie dann jedes Wort, machen Sie den ersten Buchstaben groß und den Rest klein. Fügen Sie schließlich die Wörter wieder zusammen.

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

Two Sum Problem

Gegeben ist ein Array von Ganzzahlen nums und eine Ganzzahl target. Geben Sie die Indizes der beiden Zahlen zurück, die sich zu target addieren.

Erklärung: Ein gängiger Ansatz ist die Verwendung einer Hash-Map (oder eines JavaScript-Objekts), um Zahlen und ihre Indizes während der Iteration zu speichern. Prüfen Sie für jede Zahl, ob target - aktuelle_zahl in der Map existiert.

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 []; // Oder null, oder Fehler werfen, wenn keine Lösung } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

Einen Zähler mit Closures implementieren

Erstellen Sie eine Funktion, die eine Zählfunktion zurückgibt. Jedes Mal, wenn die zurückgegebene Funktion aufgerufen wird, soll sie einen Zähler erhöhen und zurückgeben.

Erklärung: Dies demonstriert Closures. Die innere Funktion hat Zugriff auf die Variable count des Scopes der äußeren Funktion, auch nachdem die äußere Funktion ihre Ausführung beendet hat.

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

Den ersten nicht-wiederholenden Charakter finden

Schreiben Sie eine Funktion, die den ersten nicht-wiederholenden Charakter in einem String findet.

Erklärung: Sie können eine Hash-Map verwenden, um die Zeichenhäufigkeiten zu zählen. Iterieren Sie zuerst durch den String, um die Häufigkeits-Map zu erstellen. Iterieren Sie dann erneut durch den String und geben Sie das erste Zeichen mit einer Häufigkeit von 1 zurück.

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; // Oder ein Indikator, wenn sich alle wiederholen } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

Array-Chunking

Schreiben Sie eine Funktion, die ein Array in Gruppen einer angegebenen Größe aufteilt.

Erklärung: Iterieren Sie durch das Array, nehmen Sie Scheiben der angegebenen Größe und fügen Sie diese in ein neues Array ein. Behandeln Sie den Fall, dass der letzte Chunk kleiner sein könnte.

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

Prüfen, ob eine Zahl prim ist

Schreiben Sie eine Funktion, die bestimmt, ob eine gegebene Zahl eine Primzahl ist.

Erklärung: Eine Primzahl ist eine natürliche Zahl größer als 1, die keine positiven Teiler außer 1 und sich selbst hat. Iterieren Sie von 2 bis zur Quadratwurzel der Zahl und prüfen Sie auf Teilbarkeit. Behandeln Sie Randfälle wie 1 und 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

Das längste Wort in einem String finden

Schreiben Sie eine Funktion, die das längste Wort in einem Satz findet.

Erklärung: Teilen Sie den String in ein Array von Wörtern auf. Iterieren Sie dann durch das Array und verfolgen Sie das bisher längste gefundene Wort (oder dessen Länge).

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // Wörter bei Bedarf bereinigen (z.B. Satzzeichen entfernen) if (word.length > longestWord.length) { longestWord = word; } } return longestWord; } console.log(findLongestWord('The quick brown fox jumped over the lazy dog')); // 'jumped'

`Array.prototype.map` implementieren

Implementieren Sie Ihre eigene Version der Funktion Array.prototype.map.

Erklärung: Die map-Funktion nimmt einen Callback entgegen und erstellt ein neues Array, indem sie den Callback auf jedes Element des ursprünglichen Arrays anwendet. Ihre Funktion sollte das Array durchlaufen und das neue Array aufbauen.

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]

`Array.prototype.filter` implementieren

Implementieren Sie Ihre eigene Version der Funktion Array.prototype.filter.

Erklärung: Die filter-Funktion nimmt einen Callback entgegen und erstellt ein neues Array mit allen Elementen, die den vom bereitgestellten Callback implementierten Test bestehen.

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

`Array.prototype.reduce` implementieren

Implementieren Sie Ihre eigene Version der Funktion Array.prototype.reduce.

Erklärung: Die reduce-Funktion führt eine vom Benutzer bereitgestellte 'Reducer'-Callback-Funktion auf jedem Element des Arrays aus und übergibt den Rückgabewert der Berechnung des vorhergehenden Elements. Das Endergebnis der Ausführung des Reducers über alle Elemente des Arrays ist ein einzelner Wert. Behandeln Sie das Argument für den Anfangswert.

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

Memoization - Fibonacci-Sequenz

Implementieren Sie eine Fibonacci-Funktion unter Verwendung von Memoization zur Leistungsoptimierung.

Erklärung: Die Fibonacci-Sequenz beinhaltet wiederholte Berechnungen. Memoization speichert die Ergebnisse teurer Funktionsaufrufe und gibt das zwischengespeicherte Ergebnis zurück, wenn dieselben Eingaben erneut auftreten.

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 (viel schneller als nicht-memoisiert)

Prüfen auf ausgewogene Klammern

Schreiben Sie eine Funktion, die prüft, ob ein String, der Klammern {}[]() enthält, ausgewogen ist.

Erklärung: Verwenden Sie einen Stack. Wenn Sie eine öffnende Klammer finden, legen Sie sie auf den Stack. Wenn Sie eine schließende Klammer finden, prüfen Sie, ob der Stack leer ist oder ob das oberste Element die passende öffnende Klammer ist. Wenn es übereinstimmt, nehmen Sie es vom Stack. Wenn nicht, oder wenn der Stack leer ist, ist es unausgewogen. Am Ende bedeutet ein leerer Stack ausgewogen.

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

Eine einfache Warteschlange implementieren

Implementieren Sie eine Warteschlange-Datenstruktur mit den Methoden enqueue (hinten hinzufügen) und dequeue (vorne entfernen).

Erklärung: Eine Warteschlange folgt dem First-In, First-Out (FIFO)-Prinzip. Ein Array kann verwendet werden, wobei push für enqueue und shift für dequeue dient.

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

Einen einfachen Stack implementieren

Implementieren Sie eine Stack-Datenstruktur mit den Methoden push (oben hinzufügen) und pop (oben entfernen).

Erklärung: Ein Stack folgt dem Last-In, First-Out (LIFO)-Prinzip. Ein Array kann verwendet werden, mit den Methoden push und pop.

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

Die fehlende Zahl in einer Sequenz finden

Gegeben ist ein Array, das n verschiedene Zahlen aus 0, 1, 2, ..., n enthält. Finden Sie die Zahl, die im Array fehlt.

Erklärung: Die Summe der Zahlen von 0 bis n kann mit der Formel n*(n+1)/2 berechnet werden. Die tatsächliche Summe der Array-Elemente kann berechnet werden. Die Differenz zwischen diesen beiden Summen ist die fehlende Zahl.

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

Debounce-Funktion

Implementieren Sie eine Debounce-Funktion. Debouncing stellt sicher, dass eine Funktion erst wieder aufgerufen wird, nachdem eine bestimmte Zeit verstrichen ist, ohne dass sie aufgerufen wurde.

Erklärung: Verwenden Sie setTimeout und clearTimeout. Jedes Mal, wenn die Debounce-Funktion aufgerufen wird, löschen Sie den vorherigen Timeout und setzen einen neuen. Der tatsächliche Funktionsaufruf erfolgt erst, wenn der Timeout abgeschlossen ist.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // Beispielverwendung: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // Aufruf nach 1 Sek. (wenn nicht erneut aufgerufen) debouncedHello(); // Setzt Timer zurück debouncedHello(); // Setzt Timer zurück, wird tatsächlich 1 Sek. nach diesem Aufruf ausgeführt.

Throttle-Funktion

Implementieren Sie eine Throttle-Funktion. Throttling stellt sicher, dass eine Funktion höchstens einmal in einem bestimmten Zeitintervall aufgerufen wird.

Erklärung: Verwenden Sie ein Flag, um anzuzeigen, ob ein Aufruf erlaubt ist. Wenn aufgerufen, falls erlaubt, führen Sie die Funktion aus, setzen Sie das Flag auf false und verwenden Sie setTimeout, um das Flag nach dem Intervall zurückzusetzen.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // Beispielverwendung: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // Wird höchstens einmal pro Sekunde protokolliert.

Currying-Funktion

Schreiben Sie eine Funktion, die eine Funktion mit zwei Argumenten nimmt und eine Curried-Version davon zurückgibt.

Erklärung: Currying transformiert eine Funktion mit mehreren Argumenten in eine Sequenz von Funktionen, die jeweils ein einzelnes Argument nehmen. f(a, b) wird zu 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

Ein Objekt tief klonen

Schreiben Sie eine Funktion, die eine tiefe Klonierung eines JavaScript-Objekts durchführt.

Erklärung: Ein flacher Klon kopiert nur die Eigenschaften der obersten Ebene. Ein tiefer Klon kopiert rekursiv alle Eigenschaften, einschließlich verschachtelter Objekte und Arrays. Eine einfache Methode (mit Einschränkungen, z.B. behandelt keine Funktionen, Daten, Regex gut) ist die Verwendung von JSON.parse(JSON.stringify(obj)). Ein rekursiver Ansatz ist robuster.

function deepClone(obj) { // Einfache Version (mit Einschränkungen) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Klonen fehlgeschlagen:", e); return null; } // Robustere rekursive (Basispiel): /* 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 (beweist, dass es ein tiefer Klon ist) console.log(cloned.b.c); // 3

Objektschlüssel abrufen

Wie erhält man ein Array von Schlüsseln aus einem Objekt?

Erklärung: Verwenden Sie Object.keys(obj).

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

Objektwerte abrufen

Wie erhält man ein Array von Werten aus einem Objekt?

Erklärung: Verwenden Sie Object.values(obj).

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

Prüfen, ob Array Wert enthält

Wie prüft man, ob ein Array einen bestimmten Wert enthält?

Erklärung: Verwenden Sie Array.prototype.includes(value).

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

Zwei Arrays zusammenführen

Wie führt man zwei Arrays zu einem zusammen?

Erklärung: Verwenden Sie den Spread-Syntax (...) oder Array.prototype.concat().

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

'this' im globalen Geltungsbereich erklären

Worauf bezieht sich this im globalen Geltungsbereich in einem Browser?

Erklärung: Im globalen Ausführungskontext (außerhalb jeder Funktion) bezieht sich this auf das globale Objekt, welches in Webbrowsern window ist.

console.log(this === window); // true (in einer Browserumgebung)

'this' in einer Objektmethode erklären

Worauf bezieht sich this, wenn es innerhalb einer Objektmethode verwendet wird?

Erklärung: Wenn eine Funktion als Methode eines Objekts aufgerufen wird, bezieht sich this auf das Objekt, für das die Methode aufgerufen wird.

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

'this' bei Arrow-Funktionen erklären

Wie handhaben Arrow-Funktionen this?

Erklärung: Arrow-Funktionen haben keinen eigenen this-Kontext. Stattdessen erben sie this aus dem umgebenden (lexikalischen) Geltungsbereich, in dem sie definiert sind.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // Gibt 42 aus, weil 'this' geerbt wird }, 100); } new MyClass();

Unterschied zwischen `let`, `const` und `var`

Was sind die Hauptunterschiede zwischen let, const und var?

Erklärung: var ist funktionsgebunden und wird 'gehoben' (hoisted). let und const sind blockgebunden und werden ebenfalls 'gehoben', befinden sich aber bis zur Deklaration in einer 'temporal dead zone'. const muss initialisiert werden und kann nicht neu zugewiesen werden.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // Neu deklariert und beeinflusst das äußere 'a' let b = 20; // Neues 'b' innerhalb des Blocks // const c = 30; // Wäre ein neues 'c' console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

Was ist ein Promise?

Erklären Sie, was ein Promise in JavaScript ist.

Erklärung: Ein Promise ist ein Objekt, das die eventuelle Vollendung (oder das Scheitern) einer asynchronen Operation und ihren Ergebniswert darstellt. Es kann sich in einem von drei Zuständen befinden: ausstehend (pending), erfüllt (fulfilled) oder abgelehnt (rejected).

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

`async/await` verwenden

Wie vereinfachen async und await die Arbeit mit Promises?

Erklärung: async/await bietet syntaktischen Zucker über Promises, wodurch asynchroner Code ein wenig mehr wie synchroner Code aussieht und sich verhält. Eine async-Funktion gibt immer ein Promise zurück, und await pausiert die Ausführung, bis ein Promise sich entschieden hat.

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

String in Zahl umwandeln

Wie wandelt man einen String in eine Zahl um?

Erklärung: Verwenden Sie parseInt(), parseFloat() oder den unären Plusoperator (+).

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

Zahl in String umwandeln

Wie wandelt man eine Zahl in einen String um?

Erklärung: Verwenden Sie String(), number.toString() oder String-Konkatenation.

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

Was ist `JSON.stringify`?

Was macht JSON.stringify?

Erklärung: Es konvertiert ein JavaScript-Objekt oder einen Wert in einen JSON-String.

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

Was ist `JSON.parse`?

Was macht JSON.parse?

Erklärung: Es parst einen JSON-String und konstruiert den JavaScript-Wert oder das Objekt, das durch den String beschrieben wird.

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

Spread-Operator in Arrays

Wie wird der Spread-Operator mit Arrays verwendet?

Erklärung: Er ermöglicht es, ein iterierbares Objekt (wie ein Array) an Stellen zu erweitern, an denen null oder mehr Argumente oder Elemente erwartet werden. Nützlich zum Kopieren, Zusammenführen und Hinzufügen von Elementen.

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

Spread-Operator in Objekten

Wie wird der Spread-Operator mit Objekten verwendet?

Erklärung: Er ermöglicht das Kopieren und Zusammenführen von Objekteigenschaften.

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 }

Array-Destrukturierung

Erklären Sie Array-Destrukturierung mit einem Beispiel.

Erklärung: Es ist eine Syntax, die es ermöglicht, Werte aus Arrays in einzelne Variablen zu entpacken.

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

Objekt-Destrukturierung

Erklären Sie Objekt-Destrukturierung mit einem Beispiel.

Erklärung: Es ermöglicht, Eigenschaften von Objekten in einzelne Variablen zu entpacken.

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'

`call` implementieren

Wie könnten Sie eine grundlegende Version von Function.prototype.call implementieren?

Erklärung: call ruft eine Funktion mit einem angegebenen this-Wert und einzeln bereitgestellten Argumenten auf. Sie können die Funktion an den this-Kontext anhängen, sie aufrufen und dann wieder entfernen.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // Verwenden Sie einen eindeutigen Schlüssel context[uniqueId] = this; const result = context[uniqueId](...args); delete context[uniqueId]; return result; } function greet(greeting, punctuation) { console.log(`${greeting}, ${this.name}${punctuation}`); } greet.myCall({ name: 'Charlie' }, 'Hi', '!'); // Hi, Charlie!

`apply` implementieren

Wie könnten Sie eine grundlegende Version von Function.prototype.apply implementieren?

Erklärung: apply ähnelt call, akzeptiert aber Argumente als Array.

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

Event Loop erklären

Erklären Sie kurz den JavaScript Event Loop.

Erklärung: JavaScript ist Single-Threaded, erreicht aber Konkurrenz durch die Verwendung eines Event Loops. Der Call Stack verarbeitet synchronen Code. Web APIs verarbeiten asynchrone Operationen (wie setTimeout, fetch). Wenn eine asynchrone Operation abgeschlossen ist, wird ihr Callback in die Callback-Queue (oder Microtask-Queue für Promises) verschoben. Der Event Loop prüft ständig, ob der Call Stack leer ist; wenn ja, verschiebt er den nächsten Callback aus der Queue in den Stack zur Ausführung.

console.log('Start'); setTimeout(() => { console.log('Timeout Callback'); // Geht in die Callback-Queue }, 0); Promise.resolve().then(() => { console.log('Promise Resolved'); // Geht in die Microtask-Queue }); console.log('End'); // Ausgabereihenfolge: Start, End, Promise Resolved, Timeout Callback // (Microtasks laufen vor Macrotasks/Callbacks)

Binäre Suche

Implementieren Sie eine binäre Suchfunktion für ein sortiertes Array.

Erklärung: Die binäre Suche findet ein Element in einem sortierten Array effizient, indem sie das Suchintervall wiederholt halbiert. Wenn der Wert des Suchschlüssels kleiner ist als das Element in der Mitte des Intervalls, wird das Intervall auf die untere Hälfte eingeengt. Andernfalls wird es auf die obere Hälfte eingeengt. Dies geschieht, bis der Wert gefunden ist oder das Intervall leer ist.

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; // Gefunden } else if (sortedArray[middle] < key) { start = middle + 1; // Rechte Hälfte durchsuchen } else { end = middle - 1; // Linke Hälfte durchsuchen } } return -1; // Nicht gefunden } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort

Implementieren Sie den Merge Sort Algorithmus.

Erklärung: Merge Sort ist ein Divide-and-Conquer-Algorithmus. Er teilt das Eingabearray in zwei Hälften, ruft sich selbst für die beiden Hälften auf und führt dann die beiden sortierten Hälften zusammen. Die Merge-Funktion ist entscheidend; sie kombiniert zwei sortierte Teil-Arrays zu einem sortierten Array.

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

Quick Sort

Implementieren Sie den Quick Sort Algorithmus.

Erklärung: Quick Sort ist ebenfalls ein Divide-and-Conquer-Algorithmus. Er wählt ein Element als Pivot und partitioniert das gegebene Array um den gewählten Pivot. Elemente, die kleiner als der Pivot sind, gehen nach links, und Elemente, die größer sind, gehen nach rechts. Anschließend werden die Teil-Arrays rekursiv sortiert.

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

Bubble Sort

Implementieren Sie den Bubble Sort Algorithmus.

Erklärung: Bubble Sort ist ein einfacher Sortieralgorithmus, der wiederholt die Liste durchläuft, benachbarte Elemente vergleicht und sie vertauscht, wenn sie in der falschen Reihenfolge sind. Der Durchlauf durch die Liste wird wiederholt, bis die Liste sortiert ist.

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]]; // Vertauschen swapped = true; } } n--; // Optimierung: letztes Element ist bereits an Ort und Stelle } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Längste Unterzeichenkette ohne sich wiederholende Zeichen

Gegeben ist ein String. Finden Sie die Länge der längsten Unterzeichenkette ohne sich wiederholende Zeichen.

Erklärung: Verwenden Sie die 'Sliding Window'-Technik. Verwalten Sie ein Fenster (Unterzeichenkette) und einen Satz von Zeichen in diesem Fenster. Erweitern Sie das Fenster, indem Sie den rechten Zeiger bewegen. Wenn ein sich wiederholendes Zeichen gefunden wird, verkleinern Sie das Fenster von links, bis das sich wiederholende Zeichen entfernt ist.

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

Eine verkettete Liste implementieren

Implementieren Sie eine einfach verkettete Liste mit add- und print-Methoden.

Erklärung: Eine verkettete Liste ist eine lineare Datenstruktur, bei der Elemente nicht an zusammenhängenden Speicheradressen gespeichert werden. Jedes Element (Knoten) zeigt auf das nächste. Sie benötigen eine Node-Klasse und eine LinkedList-Klasse, um den Kopf und das Hinzufügen neuer Knoten zu verwalten.

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

Einen Binären Suchbaum (BST) implementieren

Implementieren Sie einen Binären Suchbaum mit insert- und find-Methoden.

Erklärung: Ein BST ist eine knotenbasierte binäre Baumdatenstruktur, die folgende Eigenschaften hat: Der linke Unterbaum eines Knotens enthält nur Knoten mit Schlüsseln, die kleiner sind als der Schlüssel des Knotens. Der rechte Unterbaum eines Knotens enthält nur Knoten mit Schlüsseln, die größer sind als der Schlüssel des Knotens. Beide Unterbäume müssen ebenfalls binäre Suchbäume sein.

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

Ein Array rotieren

Schreiben Sie eine Funktion, die ein Array um k Schritte nach rechts rotiert.

Erklärung: Das Rotieren eines Arrays bedeutet, seine Elemente zu verschieben. Bei einer Rechtsrotation wird das letzte Element zum ersten. Dies k-mal zu wiederholen, funktioniert, ist aber ineffizient. Eine bessere Methode ist die Verwendung von Array-Manipulationen oder die Berechnung der neuen Positionen.

function rotateArray(nums, k) { k = k % nums.length; // Behandelt Fälle, in denen k > Länge 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]

Schnittmenge zweier Arrays finden

Gegeben sind zwei Arrays. Schreiben Sie eine Funktion, die deren Schnittmenge (Elemente, die beiden gemeinsam sind) berechnet.

Erklärung: Ein guter Ansatz ist es, ein Array in ein Set zu konvertieren, um O(1) durchschnittliche Suchzeiten zu erzielen. Dann iterieren Sie durch das zweite Array und prüfen, ob jedes Element im Set existiert. Sammeln Sie die Übereinstimmungen.

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]

Anagramme gruppieren

Gegeben ist ein Array von Strings. Gruppieren Sie Anagramme zusammen.

Erklärung: Der Schlüssel ist, eine eindeutige Signatur für jede Gruppe von Anagrammen zu finden. Eine gängige Methode ist, jedes Wort alphabetisch zu sortieren. Wörter, die denselben sortierten String ergeben, sind Anagramme. Verwenden Sie eine Hash-Map, bei der die Schlüssel sortierte Wörter und die Werte Arrays von Originalwörtern sind.

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

Nullen ans Ende verschieben

Gegeben ist ein Array nums. Schreiben Sie eine Funktion, die alle 0en an das Ende des Arrays verschiebt, während die relative Reihenfolge der Nicht-Null-Elemente beibehalten wird.

Erklärung: Verwenden Sie einen 'Zwei-Zeiger'- oder 'Schneeball'-Ansatz. Ein Zeiger verfolgt die Position, an die das nächste Nicht-Null-Element gehen soll. Iterieren Sie durch das Array; wenn ein Element ungleich Null ist, platzieren Sie es an der Position des Zeigers und erhöhen Sie den Zeiger. Füllen Sie schließlich den Rest mit Nullen auf.

function moveZeroes(nums) { let nonZeroIndex = 0; // Verschieben Sie alle Nicht-Null-Elemente an den Anfang for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Füllen Sie den Rest mit Nullen auf 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]

Maximum Subarray (Kadane-Algorithmus)

Gegeben ist ein Array von Ganzzahlen nums. Finden Sie das zusammenhängende Teilarray (das mindestens eine Zahl enthält), das die größte Summe hat, und geben Sie dessen Summe zurück.

Erklärung: Der Kadane-Algorithmus ist eine effiziente Methode zur Lösung dieses Problems. Iterieren Sie durch das Array und verfolgen Sie die currentMax-Summe, die an der aktuellen Position endet, und die bisher gefundene globalMax-Summe. Wenn currentMax negativ wird, setzen Sie es auf 0 zurück (oder besser gesagt, auf das aktuelle Element).

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

Permutationen eines Strings

Schreiben Sie eine Funktion, die alle Permutationen eines gegebenen Strings erzeugt.

Erklärung: Dies wird typischerweise mit Rekursion und Backtracking gelöst. Fixieren Sie für jedes Zeichen ein Zeichen und generieren Sie rekursiv Permutationen für den Rest des Strings. Basisfall: Wenn der String nur ein Zeichen hat, geben Sie es zurück.

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)]; // Verwenden Sie Set, um doppelte Zeichen bei Bedarf zu behandeln } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Größter gemeinsamer Teiler (GGT)

Schreiben Sie eine Funktion, die den größten gemeinsamen Teiler (GGT) zweier Zahlen findet.

Erklärung: Der Euklidische Algorithmus ist eine effiziente Methode. Wenn b 0 ist, ist a der GGT. Andernfalls ist der GGT von a und b derselbe wie der GGT von b und a % b (der Rest von a geteilt durch 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

Kleinstes gemeinsames Vielfaches (KGV)

Schreiben Sie eine Funktion, die das kleinste gemeinsame Vielfache (KGV) zweier Zahlen findet.

Erklärung: Das KGV kann mit dem GGT und der Formel berechnet werden: KGV(a, b) = |a * b| / GGT(a, b).

function gcd(a, b) { // Helfer aus dem vorherigen 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

Promise.all implementieren

Implementieren Sie eine Funktion, die sich wie Promise.all verhält.

Erklärung: Promise.all nimmt ein Array von Promises entgegen und gibt ein einzelnes Promise zurück, das aufgelöst wird, wenn alle Eingabe-Promises aufgelöst wurden, oder abgelehnt wird, wenn ein Eingabe-Promise abgelehnt wird. Der aufgelöste Wert ist ein Array der aufgelösten Werte.

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); // Sofort bei jedem Fehler ablehnen }); }); } // Beispielverwendung: 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']

Binären Baum invertieren

Schreiben Sie eine Funktion, die einen binären Baum invertiert.

Erklärung: Um einen binären Baum zu invertieren, tauschen Sie die linken und rechten Kinder für jeden Knoten. Dies kann rekursiv oder iterativ (mit einer Warteschlange oder einem Stack) erfolgen.

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; } // Tauschen Sie die Kinder [root.left, root.right] = [root.right, root.left]; // Rekursiver Aufruf für linke und rechte Kinder invertTree(root.left); invertTree(root.right); return root; } // Beispiel: 4 -> [2, 7] -> [1, 3, 6, 9] // Wird zu: 4 -> [7, 2] -> [9, 6, 3, 1]

Maximale Tiefe eines Binärbaums

Gegeben ist ein binärer Baum. Finden Sie seine maximale Tiefe.

Erklärung: Die maximale Tiefe ist die Anzahl der Knoten entlang des längsten Pfades vom Wurzelknoten bis zum entferntesten Blattknoten. Dies kann rekursiv gefunden werden: MaxTiefe = 1 + max(MaxTiefe(links), MaxTiefe(rechts)). Der Basisfall ist, wenn ein Knoten null ist, seine Tiefe ist 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; } // Beispiel: 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

Bester Zeitpunkt zum Kaufen/Verkaufen von Aktien

Sie erhalten ein Array prices, wobei prices[i] der Preis einer bestimmten Aktie am i-ten Tag ist. Sie möchten Ihren Gewinn maximieren, indem Sie einen einzigen Tag zum Kauf einer Aktie und einen anderen Tag in der Zukunft zum Verkauf dieser Aktie wählen. Geben Sie den maximalen Gewinn zurück.

Erklärung: Iterieren Sie durch die Preise und verfolgen Sie den bisher niedrigsten gefundenen Preis (minPrice) und den bisher maximalen Gewinn (maxProfit). Berechnen Sie für jeden Tag den potenziellen Gewinn, wenn Sie heute verkaufen würden (aktueller Preis - minPrice), und aktualisieren Sie maxProfit, wenn dieser höher ist.

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 (Kaufen bei 1, Verkaufen bei 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (Kein Gewinn möglich)

Einzelne Zahl

Gegeben ist ein nicht leeres Array von Ganzzahlen nums. Jedes Element erscheint zweimal, außer einem. Finden Sie dieses einzelne Element.

Erklärung: Eine sehr effiziente Methode zur Lösung dieses Problems ist die Verwendung des bitweisen XOR-Operators (^). Das XOR-Verknüpfen einer Zahl mit sich selbst ergibt 0. Das XOR-Verknüpfen einer Zahl mit 0 ergibt die Zahl selbst. Wenn Sie alle Zahlen im Array XOR-Verknüpfen, heben sich die Paare auf (werden 0) und es bleibt nur die einzelne Zahl übrig.

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

Mehrheitselement

Gegeben ist ein Array nums der Größe n. Geben Sie das Mehrheitselement zurück. Das Mehrheitselement ist das Element, das mehr als ⌊n / 2⌋ Mal vorkommt.

Erklärung: Der Boyer-Moore-Wahlalgorithmus ist eine effiziente Methode. Initialisieren Sie einen candidate und einen count. Iterieren Sie durch das Array. Wenn count 0 ist, setzen Sie das aktuelle Element als candidate. Wenn das aktuelle Element mit dem candidate übereinstimmt, erhöhen Sie count; andernfalls verringern Sie 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

Treppensteigen

Sie steigen eine Treppe hinauf. Es dauert n Schritte, um die Spitze zu erreichen. Jedes Mal können Sie entweder 1 oder 2 Schritte steigen. Auf wie viele verschiedene Arten können Sie die Spitze erreichen?

Erklärung: Dies ist ein klassisches Problem der dynamischen Programmierung, sehr ähnlich der Fibonacci-Sequenz. Die Anzahl der Wege, um Schritt n zu erreichen, ist die Summe der Wege, um Schritt n-1 zu erreichen (indem man 1 Schritt macht) und der Wege, um Schritt n-2 zu erreichen (indem man 2 Schritte macht). 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

Produkt des Arrays außer sich selbst

Gegeben ist ein Array von Ganzzahlen nums. Geben Sie ein Array answer zurück, sodass answer[i] gleich dem Produkt aller Elemente von nums außer nums[i] ist. Verwenden Sie den Divisionsoperator nicht.

Erklärung: Sie können dies lösen, indem Sie Präfixprodukte und Suffixprodukte berechnen. Das Ergebnis an Index i ist das Produkt aller Elemente vor i multipliziert mit dem Produkt aller Elemente nach i.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Präfixprodukte berechnen let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Suffixprodukte berechnen und mit Präfix multiplizieren 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]

Anzahl der Inseln

Gegeben ist ein 2D-Gitter aus '1'en (Land) und '0'en (Wasser). Zählen Sie die Anzahl der Inseln. Eine Insel ist von Wasser umgeben und wird durch das Verbinden benachbarter Landflächen horizontal oder vertikal gebildet.

Erklärung: Iterieren Sie durch jede Zelle des Gitters. Wenn Sie eine '1' (Land) finden, erhöhen Sie die Inselanzahl und starten Sie eine Tiefensuche (DFS) oder Breitensuche (BFS) von dieser Zelle aus. Markieren Sie während der Suche alle verbundenen '1'en als '0' (oder besucht), um ein erneutes Zählen zu vermeiden.

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'; // Als besucht markieren 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 (Muss auf einer Kopie ausgeführt oder Gitter wiederhergestellt werden)

LRU Cache

Entwerfen und implementieren Sie eine Datenstruktur für einen Least Recently Used (LRU) Cache. Sie sollte die Operationen get und put unterstützen.

Erklärung: Ein LRU-Cache entfernt das am wenigsten kürzlich verwendete Element, wenn die Kapazität erreicht ist. Eine gängige Implementierung verwendet eine Map (oder eine Hash-Map) für O(1)-Suchvorgänge und eine doppelt verkettete Liste für O(1)-Updates des 'kürzlich verwendet'-Status. JavaScripts Map behält die Einfügungsreihenfolge bei, was eine grundlegende Implementierung vereinfachen kann.

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); // Entfernen, um am 'Ende' (zuletzt verwendet) erneut einzufügen 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); // Entfernt 2 console.log(cache.get(2)); // -1

Klammer-Kombinationen generieren

Gegeben ist n Paare von Klammern. Schreiben Sie eine Funktion, die alle Kombinationen von wohlgeformten Klammern generiert.

Erklärung: Dies ist ein klassisches Backtracking-Problem. Führen Sie Zähler für offene und geschlossene Klammern. Sie können eine öffnende Klammer hinzufügen, wenn open < n. Sie können eine schließende Klammer hinzufügen, wenn close < open. Der Basisfall ist, wenn die Stringlänge 2 * n erreicht.

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)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Behälter mit dem meisten Wasser

Gegeben sind n nicht-negative Ganzzahlen a1, a2, ..., an, wobei jede einen Punkt an der Koordinate (i, ai) darstellt. n vertikale Linien werden so gezeichnet, dass die beiden Endpunkte der Linie i bei (i, ai) und (i, 0) liegen. Finden Sie zwei Linien, die zusammen mit der x-Achse einen Behälter bilden, sodass der Behälter das meiste Wasser enthält.

Erklärung: Verwenden Sie den Zwei-Zeiger-Ansatz. Beginnen Sie mit einem Zeiger am Anfang und einem am Ende. Berechnen Sie die Fläche. Die Fläche wird durch die kürzere Linie begrenzt. Um möglicherweise eine größere Fläche zu finden, verschieben Sie den Zeiger, der auf die kürzere Linie zeigt, nach innen.

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

Gegeben ist ein Array nums von n Ganzzahlen. Gibt es Elemente a, b, c in nums, sodass a + b + c = 0 ist? Finden Sie alle einzigartigen Triplets im Array, die die Summe Null ergeben.

Erklärung: Sortieren Sie zuerst das Array. Dann iterieren Sie mit einem Zeiger i durch das Array. Verwenden Sie für jedes i zwei weitere Zeiger, left (beginnend bei i+1) und right (beginnend am Ende), um zwei Zahlen zu finden, die sich zu -nums[i] addieren. Behandeln Sie Duplikate, um eindeutige Triplets sicherzustellen.

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; // Überspringe Duplikate für 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++; // Überspringe Duplikate while (left < right && nums[right] === nums[right - 1]) right--; // Überspringe Duplikate 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]]

Such-Einfügeposition

Gegeben ist ein sortiertes Array von eindeutigen Ganzzahlen und ein Zielwert. Geben Sie den Index zurück, wenn das Ziel gefunden wird. Wenn nicht, geben Sie den Index zurück, an dem es eingefügt werden würde.

Erklärung: Dies ist eine Variante der binären Suche. Führen Sie eine binäre Suche durch, aber wenn das Element nicht gefunden wird, landet der start-Zeiger an der Position, an der das Element eingefügt werden sollte.

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; // Wenn nicht gefunden, ist start der Einfügepunkt } 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

Zwei sortierte Listen zusammenführen (verkettete Listen)

Führen Sie zwei sortierte verkettete Listen zusammen und geben Sie sie als neue sortierte Liste zurück. Die neue Liste sollte durch das Zusammenfügen der Knoten der ersten beiden Listen erstellt werden.

Erklärung: Verwenden Sie einen Dummy-Kopfknoten, um den Prozess zu vereinfachen. Verwenden Sie einen Zeiger, um die neue Liste aufzubauen. Vergleichen Sie die aktuellen Knoten beider Eingabelisten und fügen Sie den kleineren an die neue Liste an, wobei Sie den Zeiger dieser Liste vorwärts bewegen. Wiederholen Sie dies, bis eine Liste erschöpft ist, und hängen Sie dann den Rest der anderen Liste an.

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; } // Beispiel: 1->2->4 und 1->3->4 => 1->1->2->3->4->4

Symmetrischer Baum

Gegeben ist ein binärer Baum. Überprüfen Sie, ob er ein Spiegelbild seiner selbst ist (d.h. symmetrisch um sein Zentrum).

Erklärung: Dies kann rekursiv gelöst werden. Ein Baum ist symmetrisch, wenn der linke Unterbaum ein Spiegelbild des rechten Unterbaums ist. Erstellen Sie eine Helferfunktion isMirror(t1, t2), die überprüft, ob zwei Bäume Spiegelbilder sind. Sie sollte überprüfen: 1. t1.val === t2.val. 2. t1.left ist ein Spiegelbild von t2.right. 3. t1.right ist ein Spiegelbild von 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); } // Beispiel: 1 -> [2, 2] -> [3, 4, 4, 3] ist symmetrisch.

Level Order Traversal (BST/Baum)

Gegeben ist ein binärer Baum. Geben Sie die Level-Order-Traversal seiner Knotenwerte zurück (d.h. von links nach rechts, Ebene für Ebene).

Erklärung: Dies ist eine Breitensuche (BFS). Verwenden Sie eine Warteschlange. Beginnen Sie damit, die Wurzel zur Warteschlange hinzuzufügen. Solange die Warteschlange nicht leer ist, verarbeiten Sie alle Knoten auf der aktuellen Ebene. Für jeden verarbeiteten Knoten fügen Sie seine Kinder (falls vorhanden) zur Warteschlange für die nächste Ebene hinzu.

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

Sortiertes Array in höhenbalancierten BST konvertieren

Gegeben ist ein Array, dessen Elemente in aufsteigender Reihenfolge sortiert sind. Konvertieren Sie es in einen höhenbalancierten binären Suchbaum (BST).

Erklärung: Um einen höhenbalancierten BST zu erstellen, sollten Sie das mittlere Element des Arrays als Wurzel auswählen. Bauen Sie dann rekursiv den linken Unterbaum aus der linken Hälfte des Arrays und den rechten Unterbaum aus der rechten Hälfte auf.

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); } // Beispiel: [-10, -3, 0, 5, 9] // Ausgabe: Ein Baum wie [0, -3, 9, -10, null, 5, null]

`bind` implementieren

Implementieren Sie Ihre eigene Version von Function.prototype.bind.

Erklärung: bind erstellt eine neue Funktion, die, wenn sie aufgerufen wird, ihr this-Schlüsselwort auf den bereitgestellten Wert gesetzt hat, wobei eine gegebene Reihenfolge von Argumenten allen Argumenten vorangeht, die beim Aufruf der neuen Funktion bereitgestellt werden. Verwenden Sie apply oder call innerhalb einer zurückgegebenen Funktion, die die partielle Anwendung (voreingestellte Argumente) verarbeitet.

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

Das K-te größte Element finden

Finden Sie das k-te größte Element in einem unsortierten Array. Beachten Sie, dass es sich um das k-te größte Element in der sortierten Reihenfolge handelt, nicht um das k-te verschiedene Element.

Erklärung: Ein einfacher Ansatz ist, das Array zu sortieren und dann das Element am Index n - k auszuwählen. Ein effizienterer Ansatz für große Arrays beinhaltet die Verwendung eines Min-Heaps oder eines Selektionsalgorithmus wie Quickselect.

function findKthLargest(nums, k) { // Einfacher Ansatz: Sortieren nums.sort((a, b) => b - a); // In absteigender Reihenfolge sortieren return nums[k - 1]; // Hinweis: Quickselect wäre in einem Interview effizienter, // aber das Sortieren ist schneller zu implementieren. } 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

Prüfen, ob Objekt eine Eigenschaft hat

Wie können Sie überprüfen, ob ein Objekt eine bestimmte Eigenschaft hat?

Erklärung: Sie können den in-Operator verwenden (prüft eigene und geerbte Eigenschaften), Object.prototype.hasOwnProperty.call(obj, prop) (prüft nur eigene Eigenschaften) oder einfach obj.prop !== undefined (kann bei undefined-Werten knifflig sein).

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)

Ganzzahl in römische Ziffer umwandeln

Schreiben Sie eine Funktion, um eine Ganzzahl in ihre römische Zifferndarstellung umzuwandeln.

Erklärung: Erstellen Sie eine Zuordnung von römischen Ziffern und ihren entsprechenden Werten, geordnet vom größten zum kleinsten. Iterieren Sie durch diese Zuordnung. Ziehen Sie für jeden Wert diesen so oft wie möglich von der Eingabezahl ab und fügen Sie jedes Mal die römische Ziffer hinzu.

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

Römische Ziffer in Ganzzahl umwandeln

Schreiben Sie eine Funktion, um eine römische Ziffer in eine Ganzzahl umzuwandeln.

Erklärung: Erstellen Sie eine Zuordnung von römischen Symbolen zu Werten. Iterieren Sie durch den String. Wenn der Wert des aktuellen Symbols kleiner ist als der Wert des nächsten Symbols (wie 'IV' oder 'IX'), subtrahieren Sie den aktuellen Wert; andernfalls addieren Sie ihn.

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

Ein `Set` implementieren

Implementieren Sie eine grundlegende Set-Datenstruktur (ohne das eingebaute Set zu verwenden) mit den Methoden add, has, delete und size.

Erklärung: Sie können ein JavaScript-Objekt (Hash-Map) als zugrunde liegenden Speicher verwenden. Die Schlüssel sind die Set-Elemente (Sie müssen möglicherweise handhaben, wie verschiedene Typen gespeichert und die Eindeutigkeit der Schlüssel gewährleistet werden).

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

Pascalsches Dreieck

Gegeben ist eine Ganzzahl numRows. Generieren Sie die ersten numRows des Pascalschen Dreiecks.

Erklärung: Im Pascalschen Dreieck ist jede Zahl die Summe der beiden Zahlen direkt darüber. Beginnen Sie mit [[1]]. Für jede folgende Zeile beginnen und enden Sie mit 1. Jedes mittlere Element ist die Summe des Elements am selben Index und des vorherigen Index aus der Zeile darüber.

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

Wortsuche

Gegeben ist ein 2D-Gitter und ein Wort. Finden Sie heraus, ob das Wort im Gitter existiert. Das Wort kann aus Buchstaben sequenziell benachbarter Zellen gebildet werden, wobei 'benachbart' horizontal oder vertikal benachbarte Zellen sind.

Erklärung: Dies erfordert eine Tiefensuche (DFS) mit Backtracking. Iterieren Sie durch jede Zelle. Wenn eine Zelle mit dem ersten Buchstaben des Wortes übereinstimmt, starten Sie eine DFS. Die DFS-Funktion überprüft Nachbarn und stellt sicher, dass sie mit dem nächsten Buchstaben übereinstimmen und auf dem aktuellen Pfad nicht besucht wurden. Wenn ein Pfad fehlschlägt, machen Sie die Markierung der Zelle rückgängig (Backtrack).

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // Wort gefunden if (r < 0 || c < 0 || r >= rows || c >= cols || board[r][c] !== word[index]) { return false; } const temp = board[r][c]; board[r][c] = '#'; // Als besucht markieren 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

Minimales Fenster-Substring

Gegeben sind zwei Strings s und t. Finden Sie das minimale Fenster in s, das alle Zeichen in t enthält. Wenn es kein solches Fenster in s gibt, das alle Zeichen in t abdeckt, geben Sie den leeren String "" zurück.

Erklärung: Verwenden Sie einen Sliding-Window-Ansatz mit zwei Zeigern (left und right) und Hash-Maps. Eine Map speichert die benötigten Zeichenzählungen aus t. Eine andere Map speichert die Zählungen im aktuellen Fenster. Erweitern Sie das Fenster mit right. Sobald das Fenster alle benötigten Zeichen enthält, versuchen Sie, es mit left zu verkleinern, um das minimale mögliche Fenster zu finden.

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'

Verkettete Liste umkehren

Gegeben ist der head einer einfach verketteten Liste. Kehren Sie die Liste um und geben Sie die umgekehrte Liste zurück.

Erklärung: Sie müssen die Liste durchlaufen und den next-Zeiger jedes Knotens so ändern, dass er auf den vorherigen Knoten zeigt. Behalten Sie während der Iteration die previous-, current- und next-Knoten im Auge.

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; // Nächsten Knoten speichern current.next = prev; // Zeiger des aktuellen Knotens umkehren prev = current; // prev einen Schritt vorwärts bewegen current = next; // current einen Schritt vorwärts bewegen } return prev; // Neuer Kopf ist der letzte 'prev' } // Beispiel: 1->2->3->4->5 wird zu 5->4->3->2->1

Zyklus in verketteter Liste erkennen

Gegeben ist head, der Kopf einer verketteten Liste. Bestimmen Sie, ob die verkettete Liste einen Zyklus enthält.

Erklärung: Verwenden Sie den 'Floyd's Tortoise and Hare'-Algorithmus. Verwenden Sie zwei Zeiger, einen, der sich einen Schritt auf einmal bewegt (slow), und einen, der sich zwei Schritte auf einmal bewegt (fast). Wenn es einen Zyklus gibt, wird der fast-Zeiger irgendwann den slow-Zeiger einholen.

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; // Ende erreicht, kein Zyklus slow = slow.next; fast = fast.next.next; } return true; // Zeiger haben sich getroffen, Zyklus existiert } // Beispiel: 3->2->0->-4, wobei -4 zurück auf 2 zeigt. hasCycle gibt true zurück.

`Object.create` implementieren

Implementieren Sie eine Funktion, die das Verhalten von Object.create(proto) nachahmt.

Erklärung: Object.create erstellt ein neues Objekt, das ein bestehendes Objekt als Prototyp des neu erstellten Objekts verwendet. Dies kann erreicht werden, indem eine temporäre Konstruktorfunktion erstellt, deren Prototyp auf das Eingabe-proto gesetzt und dann eine neue Instanz davon zurückgegeben wird.

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

Was ist Hoisting?

Erklären Sie Hoisting in JavaScript und geben Sie ein Beispiel.

Erklärung: Hoisting ist das Standardverhalten von JavaScript, Deklarationen vor der Codeausführung an den Anfang des aktuellen Geltungsbereichs (global oder Funktion) zu verschieben. Variablendeklarationen (var) werden gehisst und mit undefined initialisiert. Funktionsdeklarationen werden vollständig gehisst (sowohl Name als auch Body). let und const werden gehisst, aber nicht initialisiert, was zu einer Temporal Dead Zone führt.

console.log(myVar); // undefined (var wird gehisst und mit undefined initialisiert) // console.log(myLet); // ReferenceError: Cannot access 'myLet' before initialization (TDZ) myFunc(); // 'Hello!' (Funktionsdeklaration ist vollständig gehisst) var myVar = 'I am a var'; let myLet = 'I am a let'; function myFunc() { console.log('Hello!'); }

Ereignis-Bubbling und -Capturing erklären

Erklären Sie Ereignis-Bubbling und -Capturing im Kontext des DOM.

Erklärung: Dies sind zwei Phasen der Ereignisausbreitung im HTML DOM. Capturing-Phase: Das Ereignis wandert vom window zum Zielelement nach unten. Bubbling-Phase: Das Ereignis wandert vom Zielelement zurück zum window nach oben. Standardmäßig werden die meisten Ereignis-Handler während der Bubbling-Phase registriert. Sie können addEventListener(type, listener, useCapture) mit useCapture = true verwenden, um Ereignisse während der Capturing-Phase zu behandeln.

// In einer HTML-Struktur: <div><p><span>Click Me</span></p></div> // Wenn Sie auf das <span> klicken: // Capturing: window -> document -> html -> body -> div -> p -> span // Bubbling: span -> p -> div -> body -> html -> document -> window /* JS-Beispiel div.addEventListener('click', () => console.log('Div geklickt'), true); // Capturing p.addEventListener('click', () => console.log('P geklickt'), false); // Bubbling span.addEventListener('click', () => console.log('Span geklickt'), false); // Bubbling // Ausgabe wäre: Div geklickt, Span geklickt, P geklickt */

`JSON.parse` manuell implementieren (Grundlagen)

Versuchen Sie, eine sehr einfache Version von JSON.parse zu implementieren (einfache Objekte, Arrays, Strings, Zahlen behandeln).

Erklärung: Dies ist eine sehr komplexe Aufgabe in ihrer Gesamtheit, aber in einer Live-Coding-Umgebung könnten Sie gebeten werden, eine sehr vereinfachte Untermenge zu behandeln. Sie müssten den String parsen, Objekt-{}- und Array-[]-Grenzen, Schlüssel-Wert-Paare ("key": value) und grundlegende Datentypen identifizieren. eval oder new Function können dies tricksen, sind aber gefährlich. Ein echter Parser würde einen Lexer/Tokenizer und Parser verwenden.

function basicJsonParse(jsonString) { // WARNUNG: Die Verwendung von new Function ist unsicher wie eval. // Dies ist ein vereinfachtes, UNSICHERES Beispiel nur zu Demonstrationszwecken. // Eine reale Implementierung erfordert einen richtigen Parser. try { return (new Function('return ' + jsonString))(); } catch (e) { throw new SyntaxError('Ungültiges JSON: ' + e.message); } } console.log(basicJsonParse('{"a": 1, "b": ["x", "y"]}')); // { a: 1, b: ['x', 'y'] }

Ein tief verschachteltes Array glätten

Schreiben Sie eine Funktion, um ein tief verschachteltes Array zu glätten.

Erklärung: Im Gegensatz zum vorherigen Glätten (eine Ebene) muss dies jede Verschachtelungsebene behandeln. Rekursion ist eine natürliche Passung. Iterieren Sie durch das Array. Wenn ein Element ein Array ist, rufen Sie flatten rekursiv darauf auf und verketten Sie das Ergebnis. Andernfalls fügen Sie das Element hinzu.

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // ES2019+ bietet eine viel einfachere Möglichkeit: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Ein Trie (Präfixbaum) implementieren

Implementieren Sie eine Trie-Datenstruktur mit den Methoden insert, search und startsWith.

Erklärung: Ein Trie ist eine baumartige Datenstruktur, die verwendet wird, um Schlüssel in einem Datensatz von Strings effizient zu speichern und abzurufen. Jeder Knoten repräsentiert ein Zeichen, und Pfade von der Wurzel repräsentieren Wörter oder Präfixe.

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

Ein Array mischen (Fisher-Yates)

Schreiben Sie eine Funktion, um ein Array an Ort und Stelle mit dem Fisher-Yates (oder Knuth) Algorithmus zu mischen.

Erklärung: Der Fisher-Yates-Algorithmus durchläuft ein Array vom letzten Element bis zum ersten. In jeder Iteration tauscht er das aktuelle Element mit einem zufällig ausgewählten Element vom Anfang des Arrays bis zum aktuellen Element (einschließlich).

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]]; // Elemente tauschen } return arr; } console.log(shuffleArray([1, 2, 3, 4, 5])); // z.B. [3, 5, 1, 2, 4]

Funktionen komponieren

Implementieren Sie eine compose-Funktion, die mehrere Funktionen entgegennimmt und eine neue Funktion zurückgibt, die diese von rechts nach links anwendet.

Erklärung: Die Funktionskomposition (f ∘ g)(x) = f(g(x)) wendet eine Funktion auf das Ergebnis einer anderen an. Eine allgemeine compose(f, g, h) würde f(g(h(x))) bedeuten. Verwenden Sie reduceRight für eine elegante Implementierung.

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

Funktionen verketten (Pipe)

Implementieren Sie eine pipe-Funktion, die mehrere Funktionen entgegennimmt und eine neue Funktion zurückgibt, die diese von links nach rechts anwendet.

Erklärung: Ähnlich wie compose, aber die Anwendungsreihenfolge ist umgekehrt: pipe(f, g, h) bedeutet h(g(f(x))). Verwenden Sie reduce für die Implementierung.

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

Münzwechselproblem

Gegeben ist ein Array von Münzbezeichnungen und ein Betrag. Finden Sie die minimale Anzahl von Münzen, um diesen Betrag zu erreichen. Gehen Sie von einer unendlichen Versorgung jeder Münze aus.

Erklärung: Dies ist ein klassisches Problem der dynamischen Programmierung. Erstellen Sie ein Array dp, wobei dp[i] die minimal benötigten Münzen für den Betrag i speichert. dp[i] = min(dp[i - coin]) + 1 für alle Münzen.

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

Niedrigster gemeinsamer Vorfahre (BST)

Gegeben ist ein binärer Suchbaum (BST). Finden Sie den niedrigsten gemeinsamen Vorfahren (LCA) von zwei gegebenen Knoten im BST.

Erklärung: Der LCA ist der tiefste Knoten, der beide gegebenen Knoten als Nachkommen hat. In einem BST können Sie ihn finden, indem Sie von der Wurzel aus traversieren. Wenn beide Knoten kleiner als der aktuelle Knoten sind, gehen Sie nach links. Wenn beide größer sind, gehen Sie nach rechts. Wenn einer kleiner und einer größer ist (oder einer der aktuelle Knoten ist), dann ist der aktuelle Knoten der LCA.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // LCA gefunden } } return null; // Sollte in einem gültigen BST mit p und q nicht passieren } // Beispiel: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA ist 6

Binären Baum serialisieren und deserialisieren

Entwerfen Sie einen Algorithmus zum Serialisieren und Deserialisieren eines binären Baums.

Erklärung: Die Serialisierung wandelt einen Baum in einen String oder ein Array um. Die Deserialisierung rekonstruiert den Baum. Eine gängige Methode ist die Pre-Order-Traversierung (DFS). Verwenden Sie eine spezielle Markierung (z. B. '#') für Null-Knoten, um die Struktur zu erhalten.

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

`setInterval` mit `setTimeout` implementieren

Implementieren Sie eine Funktion mySetInterval, die setInterval nachahmt, aber setTimeout rekursiv verwendet.

Erklärung: setInterval führt eine Funktion wiederholt alle N Millisekunden aus. Dies kann erreicht werden, indem eine Funktion sich selbst mit setTimeout nach jeder Ausführung aufruft. Sie benötigen auch eine Möglichkeit, sie zu löschen (myClearInterval).

function mySetInterval(callback, delay, ...args) { const interval = { timerId: null }; function run() { interval.timerId = setTimeout(() => { callback.apply(this, args); run(); // Nächsten Aufruf planen }, delay); } run(); return interval; // Ein Objekt zurückgeben, um das Löschen zu ermöglichen } function myClearInterval(interval) { clearTimeout(interval.timerId); } // Beispielverwendung: let count = 0; const intervalId = mySetInterval(() => { console.log(`Hallo: ${++count}`); if (count === 3) myClearInterval(intervalId); }, 500);

Graphtraversierung (BFS & DFS)

Implementieren Sie Breitensuche (BFS) und Tiefensuche (DFS) für einen gegebenen Graphen (Adjazenzlisten-Darstellung).

Erklärung: BFS erkundet zuerst Nachbarn (mithilfe einer Warteschlange), während DFS so weit wie möglich entlang jedes Zweigs erkundet (mithilfe eines Stapels oder Rekursion). Verfolgen Sie besuchte Knoten, um Zyklen und redundante Arbeit zu vermeiden.

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); // Nachbarn in umgekehrter Reihenfolge hinzufügen, um sie später der Reihe nach zu verarbeiten (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']

Bild rotieren (Matrix)

Sie erhalten eine n x n 2D-Matrix, die ein Bild darstellt. Rotieren Sie das Bild um 90 Grad (im Uhrzeigersinn) an Ort und Stelle.

Erklärung: Eine gängige Methode hierfür ist, die Matrix zuerst zu transponieren (wobei matrix[i][j] mit matrix[j][i] vertauscht wird) und dann jede Zeile umzukehren.

function rotate(matrix) { const n = matrix.length; // Transponieren 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]]; } } // Jede Zeile umkehren 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]]

Spiralmatrix-Traversierung

Gegeben ist eine m x n Matrix. Geben Sie alle Elemente der Matrix in Spiralreihenfolge zurück.

Erklärung: Verwenden Sie vier Zeiger, um die Grenzen zu definieren: top, bottom, left, right. Durchlaufen Sie die obere Zeile, dann die rechte Spalte, dann die untere Zeile, dann die linke Spalte, wobei Sie die Grenzen nach jeder Durchquerung verkleinern, bis left right überquert oder top bottom überquert.

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]

Setze Matrix-Nullen

Gegeben ist eine m x n Matrix. Wenn ein Element 0 ist, setze seine gesamte Zeile und Spalte auf 0. Tue dies direkt (in-place).

Erklärung: Ein naiver Ansatz erfordert O(m*n) zusätzlichen Speicherplatz. Um dies in O(1) Speicherplatz (oder O(m+n)) zu tun, können Sie die erste Zeile und die erste Spalte verwenden, um Informationen darüber zu speichern, welche Zeilen/Spalten auf Null gesetzt werden müssen. Sie benötigen separate Flags dafür, ob die erste Zeile/Spalte selbst auf Null gesetzt werden muss.

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

Implementiere `Promise.race`

Implementiere eine Funktion, die sich wie Promise.race verhält.

Erklärung: Promise.race nimmt ein Array von Promises entgegen und gibt ein einzelnes Promise zurück. Dieses zurückgegebene Promise settled (erfüllt oder lehnt ab), sobald eines der Eingabe-Promises settled, mit dem Wert oder Grund dieses Promises.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // Oder resolve/reject je nach Spezifikation } 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'

Singleton-Muster

Implementiere das Singleton-Designmuster in JavaScript.

Erklärung: Das Singleton-Muster stellt sicher, dass eine Klasse nur eine Instanz hat und einen globalen Zugriffspunkt darauf bietet. Dies kann durch die Verwendung einer Closure erreicht werden, um die Instanz zu halten.

const Singleton = (function() { let instance; function createInstance() { // Private Methoden und Variablen const privateVar = 'Ich bin privat'; function privateMethod() { console.log('Privat'); } return { // Öffentliche Methoden und Variablen publicVar: 'Ich bin öffentlich', publicMethod: function() { console.log('Öffentlich'); }, 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()); // 'Ich bin privat'

IP-Adresse validieren

Schreibe eine Funktion, um zu überprüfen, ob eine gegebene Zeichenkette eine gültige IPv4- oder IPv6-Adresse ist.

Erklärung: Für IPv4: Prüfen Sie auf 4 Zahlen (0-255), getrennt durch Punkte, ohne führende Nullen (außer für '0' selbst). Für IPv6: Prüfen Sie auf 8 Gruppen von 1-4 Hex-Ziffern, getrennt durch Doppelpunkte (handhaben Sie Variationen wie '::'). Regex kann verwendet werden, aber manuelles Parsen ist für Interviews oft klarer.

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

Peak-Element finden

Ein Peak-Element ist ein Element, das streng größer ist als seine Nachbarn. Gegeben ist ein Eingabearray nums, finde ein Peak-Element und gib dessen Index zurück.

Erklärung: Da jeder Peak ausreicht und nums[-1] und nums[n] als -Infinity betrachtet werden, können Sie eine modifizierte binäre Suche verwenden. Wenn nums[mid] kleiner als nums[mid+1] ist, muss ein Peak rechts existieren. Andernfalls muss ein Peak links existieren (oder mid selbst ist ein Peak).

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 ist rechts } else { right = mid; // Peak ist mid oder links } } return left; // 'left' ist der Index eines Peaks } console.log(findPeakElement([1, 2, 3, 1])); // 2 (Index von 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (Index von 6) oder 1 (Index von 2)

Bits zählen

Gegeben ist eine ganze Zahl n. Gib ein Array ans der Länge n + 1 zurück, so dass für jedes i (0 <= i <= n), ans[i] die Anzahl der 1er in der Binärdarstellung von i ist.

Erklärung: Dies kann mit dynamischer Programmierung gelöst werden. Beachten Sie die Beziehung: dp[i] = dp[i >> 1] + (i & 1). Die Anzahl der 1er in i ist die Anzahl der 1er in i nach rechts verschoben (d.h. i/2), plus 1, wenn i ungerade ist.

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); // Oder: dp[i] = dp[i & (i - 1)] + 1; (Entfernt das letzte gesetzte Bit) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

Zweierpotenz

Gegeben ist eine ganze Zahl n. Gib true zurück, wenn es sich um eine Zweierpotenz handelt. Andernfalls gib false zurück.

Erklärung: Eine Zweierpotenz hat in der Binärdarstellung genau ein '1'-Bit (z.B. 1=1, 2=10, 4=100, 8=1000). Ein cleverer Bit-Trick ist die Überprüfung, ob n > 0 und (n & (n - 1)) === 0 ist. Wenn n eine Zweierpotenz ist, hat n-1 alle Bits unterhalb dieses '1'-Bits auf '1' gesetzt. Eine UND-Verknüpfung ergibt 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

Intervalle zusammenführen

Gegeben ist ein Array von Intervallen, wobei intervals[i] = [starti, endi]. Führe alle überlappenden Intervalle zusammen und gib ein Array der nicht überlappenden Intervalle zurück.

Erklärung: Sortiere zuerst die Intervalle nach ihren Startzeiten. Iteriere dann durch die sortierten Intervalle. Wenn das aktuelle Intervall das letzte Intervall in der Ergebnisliste überlappt, führe sie zusammen, indem du die Endzeit des letzten Intervalls aktualisierst. Andernfalls füge das aktuelle Intervall zur Ergebnisliste hinzu.

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]) { // Überlappung: zusammenführen last[1] = Math.max(last[1], current[1]); } else { // Keine Überlappung: neues Intervall hinzufügen merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

Wortumbruch

Gegeben ist eine Zeichenkette s und ein Wörterbuch wordDict von Zeichenketten. Gib true zurück, wenn s in eine durch Leerzeichen getrennte Sequenz von einem oder mehreren Wörterbuchwörtern segmentiert werden kann.

Erklärung: Verwende dynamische Programmierung. Erstelle ein boolesches Array dp, wobei dp[i] wahr ist, wenn der Substring s[0...i-1] segmentiert werden kann. dp[i] ist wahr, wenn es ein j < i gibt, so dass dp[j] wahr ist und der Substring s[j...i-1] im Wörterbuch ist.

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

Implementiere `Array.prototype.flat`

Implementiere deine eigene Version von Array.prototype.flat, die ein neues Array erstellt, in dem alle Unterarray-Elemente rekursiv bis zu einer angegebenen Tiefe verkettet sind.

Erklärung: Verwende Rekursion. Iteriere durch das Array. Wenn ein Element ein Array ist und die aktuelle Tiefe kleiner ist als die angegebene Tiefe, rufe rekursiv flatten darauf auf. Andernfalls füge das Element hinzu. Behandle die Standardtiefe von 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]

Wörter in einem String umkehren

Gegeben ist ein Eingabestring s. Kehre die Reihenfolge der Wörter um.

Erklärung: Ein 'Wort' ist als eine Folge von Nicht-Leerzeichen definiert. Entferne führende/nachfolgende Leerzeichen aus dem String, teile ihn durch ein oder mehrere Leerzeichen, filtere alle leeren Strings heraus, die durch mehrere Leerzeichen entstehen, kehre das Array um und füge es mit einzelnen Leerzeichen wieder zusammen.

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'

Query String Parser

Schreibe eine Funktion, um einen URL-Query-String in ein Objekt zu parsen.

Erklärung: Teile die Zeichenkette nach &. Für jeden Teil teile sie nach = auf, um Schlüssel und Wert zu erhalten. Dekodiere URI-Komponenten sowohl für Schlüssel als auch für Wert. Behandle Fälle, in denen ein Schlüssel mehrmals vorkommen kann (erstelle ein Array) oder keinen Wert hat.

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 }

Verwende `Proxy` zur Validierung

Demonstriere, wie ein Proxy-Objekt zur Validierung von Objekteigenschaften verwendet werden kann.

Erklärung: Ein Proxy ermöglicht es Ihnen, Operationen, die auf einem Objekt ausgeführt werden, abzufangen und anzupassen. Verwenden Sie die set-Falle, um Werte vor der Zuweisung zu einer Eigenschaft zu validieren. Lösen Sie einen Fehler aus, wenn die Validierung fehlschlägt.

const validator = { set: function(obj, prop, value) { if (prop === 'age') { if (!Number.isInteger(value)) { throw new TypeError('Das Alter ist keine ganze Zahl'); } if (value < 0 || value > 150) { throw new RangeError('Das Alter scheint ungültig zu sein'); } } // Standardverhalten: Setze die Eigenschaft 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'; // Löst TypeError aus // personProxy.age = 200; // Löst RangeError aus

Besprechungsräume

Gegeben ist ein Array von Besprechungszeitintervallen [[start1, end1], [start2, end2], ...]. Bestimme, ob eine Person alle Besprechungen besuchen könnte.

Erklärung: Wenn eine Person alle Besprechungen besuchen kann, bedeutet dies, dass sich keine zwei Besprechungen überlappen. Um dies zu überprüfen, sortieren Sie die Intervalle nach ihren Startzeiten. Iterieren Sie dann durch die sortierten Intervalle und überprüfen Sie, ob die Startzeit der aktuellen Besprechung vor der Endzeit der vorherigen Besprechung liegt. Wenn ja, gibt es eine Überlappung, und die Person kann nicht an allen teilnehmen.

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

Implementiere `Promise.any`

Implementiere eine Funktion, die sich wie Promise.any verhält.

Erklärung: Promise.any nimmt ein Array von Promises entgegen und gibt ein einzelnes Promise zurück. Dieses Promise wird erfüllt, sobald eines der Eingabe-Promises erfüllt ist. Wenn alle Eingabe-Promises abgelehnt werden, wird es mit einem AggregateError abgelehnt, der alle Ablehnungsgründe enthält.

function myPromiseAny(promises) { return new Promise((resolve, reject) => { const numPromises = promises.length; if (numPromises === 0) { reject(new AggregateError([], 'Alle Promises wurden abgelehnt')); return; } let rejectionCount = 0; const errors = []; promises.forEach((promise, index) => { Promise.resolve(promise) .then(resolve) // Löse auf, sobald einer erfüllt ist .catch(error => { errors[index] = error; rejectionCount++; if (rejectionCount === numPromises) { reject(new AggregateError(errors, 'Alle Promises wurden abgelehnt')); } }); }); }); } // Beispiel: myPromiseAny([Promise.reject('err1'), Promise.resolve('ok')]) -> 'ok' // Beispiel: myPromiseAny([Promise.reject('err1'), Promise.reject('err2')]) -> AggregateError

Beobachter-Muster

Implementiere das Beobachter-Muster (Pub/Sub) in JavaScript.

Erklärung: Das Beobachter-Muster definiert eine Eins-zu-Viele-Abhängigkeit zwischen Objekten. Wenn ein Objekt (Subjekt) seinen Zustand ändert, werden alle seine Abhängigen (Beobachter) automatisch benachrichtigt und aktualisiert. Das Subjekt verwaltet eine Liste von Beobachtern und bietet Methoden zum Hinzufügen, Entfernen und Benachrichtigen dieser an.

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

Duplikat-Zahl finden

Gegeben ist ein Array von ganzen Zahlen nums, das n + 1 ganze Zahlen enthält, wobei jede ganze Zahl im Bereich [1, n] (inklusive) liegt. Finde die eine wiederholte Zahl.

Erklärung: Da Zahlen im Bereich [1, n] liegen und die Array-Größe n+1 beträgt, ist mindestens ein Duplikat garantiert. Dies kann auf ein 'Zyklus in verketteter Liste finden'-Problem (Floyd's Tortoise and Hare) abgebildet werden. Behandeln Sie die Array-Werte als Zeiger: Index -> nums[Index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Phase 1: Schnittpunkt finden do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Phase 2: Eingang zum Zyklus finden 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

Grundlegender HTML-Sanitizer

Schreibe eine grundlegende Funktion, um einen HTML-String zu bereinigen, indem potenziell schädliche Tags (wie <script>) entfernt werden, während sichere Tags (wie <p>, <b>) beibehalten werden.

Erklärung: Dies ist ein komplexes Thema, und reale Sanitizer sind anspruchsvoll. Für ein Interview könnte ein grundlegender Ansatz die Verwendung von Regex zum Entfernen spezifischer Tags oder das Zulassen nur einer Whitelist von Tags umfassen. Dies ist nicht sicher für die Produktion.

function basicSanitize(html) { // WARNUNG: Dies ist ein sehr grundlegendes und unsicheres Beispiel. // NICHT in der Produktion verwenden. Verwenden Sie eine Bibliothek wie DOMPurify. // Skript-Tags entfernen let sanitized = html.replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, ''); // Beispiel: onclick-Attribute entfernen (sehr grundlegend) 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>

Editier-Distanz

Gegeben sind zwei Zeichenketten word1 und word2. Gib die minimale Anzahl von Operationen (Einfügen, Löschen oder Ersetzen) zurück, die erforderlich sind, um word1 in word2 umzuwandeln.

Erklärung: Dies ist ein klassisches Problem der dynamischen Programmierung (Levenshtein-Distanz). Erstellen Sie ein 2D-Array dp, wobei dp[i][j] die Editierdistanz zwischen den ersten i Zeichen von word1 und den ersten j Zeichen von word2 ist. Die Rekursionsbeziehung berücksichtigt die Kosten für Einfügen, Löschen und Ersetzen.

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, // Löschen dp[i][j - 1] + 1, // Einfügen dp[i - 1][j - 1] + cost // Ersetzen/Übereinstimmung ); } } return dp[m][n]; } console.log(minDistance('horse', 'ros')); // 3 console.log(minDistance('intention', 'execution')); // 5

Längste aufsteigende Teilfolge (LIS)

Gegeben ist ein Integer-Array nums. Gib die Länge der längsten streng monoton steigenden Teilfolge zurück.

Erklärung: Verwenden Sie dynamische Programmierung. Sei dp[i] die Länge der LIS, die bei Index i endet. Um dp[i] zu berechnen, finden Sie alle j < i, so dass nums[j] < nums[i], und nehmen Sie dp[i] = 1 + max(dp[j]). Die endgültige Antwort ist der maximale Wert im dp-Array.

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)

N-Damen-Problem

Das N-Damen-Problem ist das Problem, N Schachdamen auf einem N×N Schachbrett so zu platzieren, dass sich keine zwei Damen gegenseitig bedrohen. Gegeben N, gib eine gültige Lösung (oder alle) zurück.

Erklärung: Verwenden Sie Backtracking. Platzieren Sie Damen Zeile für Zeile. Versuchen Sie für jede Zeile, eine Dame in jeder Spalte zu platzieren. Prüfen Sie vor dem Platzieren, ob die vorgeschlagene Position sicher ist (nicht von Damen in früheren Zeilen angegriffen wird). Wenn sicher, platzieren Sie sie und rufen Sie rekursiv für die nächste Zeile auf. Wenn die Rekursion fehlschlägt, machen Sie den letzten Schritt rückgängig (entfernen Sie die Dame) und versuchen Sie die nächste Spalte.

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; // Spalte prüfen for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Diagonale oben-links prüfen for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Diagonale oben-rechts prüfen 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..' ] ]

Verwende `WeakMap` für private Daten

Demonstriere, wie WeakMap verwendet werden kann, um private Daten für Klasseninstanzen zu speichern.

Erklärung: WeakMap ermöglicht es Ihnen, Daten einem Objekt zuzuordnen, ohne die Garbage Collection zu verhindern, wenn auf das Objekt nicht mehr verwiesen wird. Dies ist nützlich, um 'private' Mitglieder in JavaScript-Klassen zu erstellen, bevor native private Felder weit verbreitet waren oder für spezifische Anwendungsfälle.

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' ist keine öffentliche Eigenschaft // console.log(privateData.get(person)); // Zugänglich, aber nicht direkt über 'person.'

Implementiere `Promise.allSettled`

Implementiere eine Funktion, die sich wie Promise.allSettled verhält.

Erklärung: Promise.allSettled nimmt ein Array von Promises entgegen und gibt ein einzelnes Promise zurück. Dieses Promise wird erfüllt, wenn alle Eingabe-Promises settled sind (entweder erfüllt oder abgelehnt). Der Erfüllungswert ist ein Array von Objekten, die jeweils das Ergebnis eines Promises beschreiben ({status: 'fulfilled', value: ...} oder {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); } }); }); }); } // Beispiel: myPromiseAllSettled([Promise.resolve(1), Promise.reject('err')]) // -> [{status: 'fulfilled', value: 1}, {status: 'rejected', reason: 'err'}]

Median zweier sortierter Arrays finden

Gegeben sind zwei sortierte Arrays nums1 und nums2 der Größe m bzw. n. Gib den Median der beiden sortierten Arrays zurück.

Erklärung: Dies ist ein schwieriges Problem, das oft mit einem binären Suchansatz auf dem kleineren Array gelöst wird. Das Ziel ist es, beide Arrays so zu partitionieren, dass alle Elemente auf der linken Seite kleiner oder gleich allen Elementen auf der rechten Seite sind und die Anzahl der Elemente auf der linken Seite gleich (oder eins mehr als) der rechten ist. Der Median kann dann aus den Randelementen berechnet werden.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Sicherstellen, dass nums1 kleiner ist 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('Eingabearrays sind nicht sortiert'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

Sudoku-Löser

Schreibe ein Programm, um ein Sudoku-Rätsel durch Ausfüllen der leeren Zellen zu lösen.

Erklärung: Verwenden Sie Backtracking. Finden Sie eine leere Zelle. Versuchen Sie, sie mit Zahlen von 1 bis 9 zu füllen. Überprüfen Sie für jede Zahl, ob sie gültig ist (nicht gegen die Sudoku-Regeln verstößt). Wenn gültig, rufen Sie den Löser rekursiv auf. Wenn die Rekursion true zurückgibt, wurde eine Lösung gefunden. Andernfalls machen Sie den letzten Schritt rückgängig (setzen Sie die Zelle zurück) und versuchen Sie die nächste Zahl.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Zeile prüfen if (board[i][col] === numStr) return false; // Spalte prüfen 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; // Box prüfen } 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; // Keine gültige Zahl gefunden } } } return true; // Brett gelöst } backtrack(); return board; } // Beispiel erfordert ein 9x9-Brett mit '.' für leere Zellen.

Implementiere ein grundlegendes Middleware-Muster

Implementiere ein einfaches Middleware-Muster, das oft in Web-Frameworks verwendet wird.

Erklärung: Middleware-Funktionen verarbeiten eine Anfrage, bevor sie den endgültigen Handler erreicht. Jede Middleware kann die Anfrage/Antwort ändern oder die Kontrolle an die nächste Middleware übergeben. Erstellen Sie eine Klasse oder ein Objekt, das eine Liste von Middleware-Funktionen verwaltet und diese sequenziell ausführt.

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

Zyklus in gerichtetem Graphen erkennen

Gegeben ist ein gerichteter Graph. Schreibe eine Funktion, um festzustellen, ob er einen Zyklus enthält.

Erklärung: Verwenden Sie die Tiefensuche (DFS). Halten Sie zwei Mengen: visiting (Knoten, die sich gerade im Rekursionsstapel befinden) und visited (Knoten, die vollständig erkundet wurden). Wenn Sie während der DFS auf einen Knoten in der visiting-Menge stoßen, haben Sie eine Rückwärtskante gefunden, was bedeutet, dass ein Zyklus vorhanden ist.

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; // Zyklus erkannt 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

Verhalten von `Object.freeze` implementieren

Erklären Sie Object.freeze und implementieren Sie eine (flache) Version.

Erklärung: Object.freeze verhindert das Hinzufügen neuer Eigenschaften, das Entfernen vorhandener Eigenschaften und das Ändern vorhandener Eigenschaften oder deren Aufzählbarkeit, Konfigurierbarkeit oder Beschreibbarkeit. Es macht ein Objekt (flach) unveränderlich. Sie können es mit Object.preventExtensions und Object.defineProperty implementieren, um vorhandene Eigenschaften nicht schreibbar und nicht konfigurierbar zu machen.

function myFreeze(obj) { Object.preventExtensions(obj); // Verhindert das Hinzufügen neuer Eigenschaften 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; // Schlägt stillschweigend fehl (oder wirft im strict mode einen Fehler) // delete obj.a; // Schlägt stillschweigend fehl (oder wirft im strict mode einen Fehler) // obj.a = 10; // Schlägt stillschweigend fehl (oder wirft im strict mode einen Fehler) console.log(obj); // { a: 1, b: 2 }

`requestAnimationFrame` für flüssige Animationen verwenden

Erklären Sie requestAnimationFrame und zeigen Sie, wie es für eine einfache Animationsschleife verwendet wird.

Erklärung: requestAnimationFrame (rAF) teilt dem Browser mit, dass Sie eine Animation durchführen möchten, und fordert den Browser auf, eine angegebene Funktion aufzurufen, um eine Animation vor dem nächsten Neuzeichnen zu aktualisieren. Es ist effizienter und flüssiger als die Verwendung von setTimeout oder setInterval für Animationen, da es mit der Aktualisierungsrate des Browsers synchronisiert und pausiert, wenn der Tab nicht sichtbar ist.

// 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; // Bewege 100px in 2 Sekunden (50px/Sek) position = (elapsedTime / 2000) * 100; if (position < 200) { // Animiert weiter, bis es 200px erreicht box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Starten Sie die Animation // requestAnimationFrame(animate); // Auskommentierung aufheben, um im Browser auszuführen console.log('Verwenden Sie requestAnimationFrame für Browseranimationen.');

`Array.prototype.some` implementieren

Implementieren Sie Ihre eigene Version von Array.prototype.some.

Erklärung: some testet, ob mindestens ein Element im Array den Test besteht, der von der bereitgestellten Funktion implementiert wurde. Es gibt true zurück, wenn es ein Element findet, für das der Callback true zurückgibt; andernfalls gibt es false zurück.

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

`Array.prototype.every` implementieren

Implementieren Sie Ihre eigene Version von Array.prototype.every.

Erklärung: every testet, ob alle Elemente im Array den Test bestehen, der von der bereitgestellten Funktion implementiert wurde. Es gibt true zurück, wenn alle Elemente bestehen (oder wenn das Array leer ist), und false, wenn ein Element fehlschlägt.

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

`Array.prototype.findIndex` implementieren

Implementieren Sie Ihre eigene Version von Array.prototype.findIndex.

Erklärung: findIndex gibt den Index des ersten Elements im Array zurück, das die bereitgestellte Testfunktion erfüllt. Andernfalls gibt es -1 zurück, was anzeigt, dass kein Element den Test bestanden hat.

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

Was sind Web Workers?

Erklären Sie, was Web Workers sind und ihren primären Anwendungsfall.

Erklärung: Web Workers sind ein Mittel für Webinhalte, um Skripte in Hintergrund-Threads auszuführen. Der Hauptanwendungsfall besteht darin, lang laufende oder rechenintensive Aufgaben vom Haupt-Thread (der UI-Updates und Benutzerinteraktionen verarbeitet) auszulagern, wodurch verhindert wird, dass die Benutzeroberfläche nicht mehr reagiert oder 'einfriert'. Worker kommunizieren mit dem Haupt-Thread über Nachrichtenübergabe (postMessage und onmessage).

// main.js /* if (window.Worker) { const myWorker = new Worker('worker.js'); myWorker.postMessage([5, 3]); // Daten an Worker senden myWorker.onmessage = function(e) { console.log('Ergebnis vom Worker:', e.data); } } else { console.log('Ihr Browser unterstützt keine Web Workers.'); } */ // worker.js /* self.onmessage = function(e) { console.log('Nachricht vom Hauptskript empfangen'); const result = e.data[0] * e.data[1]; console.log('Ergebnis wird an das Hauptskript zurückgesendet'); self.postMessage(result); } */ console.log('Web Workers führen Skripte in Hintergrund-Threads aus.');

Dekodierungsmöglichkeiten

Eine Nachricht, die Buchstaben von A-Z enthält, kann unter Verwendung der Abbildung 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26 in Zahlen kodiert werden. Gegeben ist eine Zeichenkette s, die nur Ziffern enthält. Gib die Anzahl der Möglichkeiten zurück, sie zu dekodieren.

Erklärung: Verwenden Sie dynamische Programmierung. Sei dp[i] die Anzahl der Möglichkeiten, s[0...i-1] zu dekodieren. dp[i] hängt von dp[i-1] ab (wenn s[i-1] ein gültiger 1-stelliger Code ist) und dp[i-2] (wenn s[i-2...i-1] ein gültiger 2-stelliger Code ist).

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

Bitweise Addition (ohne `+`)

Schreibe eine Funktion, um zwei ganze Zahlen ohne Verwendung des + Operators zu addieren.

Erklärung: Verwenden Sie Bitweise Operatoren. Die Summe kann als a ^ b (Summe ohne Übertrag) berechnet werden, und der Übertrag kann als (a & b) << 1 berechnet werden. Wiederholen Sie diesen Vorgang, bis der Übertrag 0 wird.

function getSum(a, b) { while (b !== 0) { const carry = (a & b) << 1; // Übertrag berechnen a = a ^ b; // Summe ohne Übertrag berechnen b = carry; // Übertrag wird zum neuen 'b' } return a; } console.log(getSum(3, 5)); // 8 console.log(getSum(-2, 3)); // 1

Auf Palindrom prüfen (Zahl)

Gegeben ist eine ganze Zahl x. Gib true zurück, wenn x ein Palindrom ist, und false andernfalls.

Erklärung: Eine negative Zahl ist kein Palindrom. Wandeln Sie die Zahl in einen String um und prüfen Sie, ob der String ein Palindrom ist (liest sich vorwärts und rückwärts gleich). Alternativ können Sie die Zahl mathematisch umkehren und vergleichen.

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

Fabrikmuster

Implementieren Sie das Fabrikmuster (Factory Pattern) in JavaScript.

Erklärung: Das Fabrikmuster bietet eine Schnittstelle zum Erstellen von Objekten in einer Oberklasse, lässt aber Unterklassen den Typ der zu erstellenden Objekte ändern. Es ist nützlich, um Objekte zu erstellen, ohne die genaue Klasse anzugeben.

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('Unbekannter Fahrzeugtyp'); } } } 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' }

`Symbol` erklären und einen Anwendungsfall nennen

Erklären Sie, was Symbol in JavaScript ist und geben Sie einen Anwendungsfall an, z. B. das Erstellen von 'privaten' Eigenschaften oder eindeutigen Objektschlüsseln.

Erklärung: Symbol ist ein primitiver Datentyp, der in ES6 eingeführt wurde. Seine Instanzen sind eindeutig und unveränderlich. Sie werden oft als Schlüssel für Objekteigenschaften verwendet, um Namenskollisionen zwischen verschiedenen Bibliotheken zu vermeiden oder um pseudo-private Eigenschaften zu erstellen (obwohl sie nicht wirklich privat sind, sind sie über typische Iterationen oder Object.keys nicht zugänglich).

const _privateName = Symbol('name'); const _privateMethod = Symbol('greet'); class Person { constructor(name) { this[_privateName] = name; } [_privateMethod]() { console.log(`Hallo, mein Name ist ${this[_privateName]}`); } introduce() { this[_privateMethod](); } } const p = new Person('Bob'); p.introduce(); // Hallo, mein Name ist Bob console.log(Object.keys(p)); // [] - Symbole sind nicht enthalten console.log(p[_privateName]); // Bob (Zugänglich, wenn Sie das Symbol haben)

`arguments` in ein echtes Array umwandeln

Wie können Sie das arguments-Objekt (verfügbar in Nicht-Pfeilfunktionen) in ein echtes JavaScript-Array umwandeln?

Erklärung: Das arguments-Objekt sieht aus wie ein Array, ist aber keins; es fehlen Methoden wie map, filter usw. Sie können es mit den folgenden Methoden umwandeln:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (ES6 Spread-Syntax)
  3. Array.prototype.slice.call(arguments) (Ältere Methode)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Mit Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread-Syntax const args2 = [...arguments]; console.log('Mit Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Mit Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Mit Array.from: 10 // Mit Spread: 10 // Mit Slice: 10