Live Coding

객체가 비어 있는지 확인

자바스크립트 객체가 비어 있는지 어떻게 확인합니까?

설명: 객체는 자체적인 열거 가능한 속성이 없으면 비어 있는 것입니다. 주어진 객체의 자체 열거 가능한 속성 이름 배열을 반환하는 Object.keys(obj)를 사용할 수 있습니다. 이 배열의 길이가 0이면 객체는 비어 있는 것입니다.

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

문자열 뒤집기

주어진 문자열을 뒤집는 함수를 작성하십시오.

설명: 가장 간단한 방법은 문자열을 문자 배열로 변환하고, 배열의 내장 reverse() 메서드를 사용한 다음, 문자를 다시 문자열로 결합하는 것입니다.

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

회문 확인

주어진 문자열이 회문인지 확인하는 함수를 작성하십시오.

설명: 회문은 앞에서 뒤로 읽으나 뒤에서 앞으로 읽으나 같은 단어 또는 구입니다. 문자열을 뒤집어서 (더 견고한 검사를 위해 대소문자와 비영숫자 문자를 무시하지만, 여기서는 간단한 검사를 수행합니다) 원본과 비교하여 이를 확인할 수 있습니다.

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

배열에서 최댓값 찾기

숫자 배열에서 가장 큰 숫자를 찾는 함수를 작성하십시오.

설명: 배열을 반복하면서 지금까지 찾은 가장 큰 숫자를 추적할 수 있습니다. 또는 스프레드 문법(...)과 함께 Math.max() 함수를 사용하여 배열 요소를 인수로 전달할 수 있습니다.

function findMaxNumber(arr) { if (arr.length === 0) return undefined; // 또는 오류 발생 return Math.max(...arr); } console.log(findMaxNumber([1, 5, 2, 9, 3])); // 9

피즈버즈

1부터 n까지 숫자를 출력하는 프로그램을 작성하십시오. 하지만 3의 배수에는 숫자 대신 'Fizz'를 출력하고, 5의 배수에는 'Buzz'를 출력하십시오. 3과 5의 공배수에는 'FizzBuzz'를 출력하십시오.

설명: 이 고전적인 문제는 기본적인 루프 및 조건 논리를 테스트합니다. 1부터 n까지 반복하고 모듈로 연산자(%)를 사용하여 3과 5로 나누어 떨어지는지 확인해야 합니다.

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

배열에서 중복 제거

배열에서 중복 요소를 제거하는 함수를 작성하십시오.

설명: 이를 달성하는 현대적이고 간결한 방법은 Set을 사용하는 것입니다. Set은 고유한 값만 저장합니다. 배열을 Set으로 변환한 다음 다시 배열로 변환할 수 있습니다.

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

애너그램 확인

두 문자열이 서로 애너그램인지 확인하는 함수를 작성하십시오.

설명: 애너그램은 다른 단어의 글자를 재배열하여 형성된 단어입니다. 확인하려면 문자열을 정리하고, 정렬하고, 비교할 수 있습니다. 정리에는 비영숫자 문자를 제거하고 일관된 대소문자로 변환하는 것이 포함됩니다.

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

팩토리얼 계산

음이 아닌 정수의 팩토리얼을 계산하는 함수를 작성하십시오.

설명: 숫자의 팩토리얼(n!)은 1부터 n까지의 모든 양의 정수를 곱한 값입니다. 1부터 n까지 숫자를 곱하여 반복적으로 계산할 수 있습니다.

function factorial(n) { if (n < 0) return undefined; // 팩토리얼은 음수에 대해 정의되지 않습니다. if (n === 0) return 1; let result = 1; for (let i = 1; i <= n; i++) { result *= i; } return result; } console.log(factorial(5)); // 120

배열의 모든 숫자 합계

배열의 모든 숫자의 합계를 반환하는 함수를 작성하십시오.

설명: reduce 메서드를 사용할 수 있습니다. 이 메서드는 배열에서 단일 값을 누적하는 데 이상적입니다. 콜백 함수와 초기값(합계를 위한 0)을 사용합니다.

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

중첩 배열 평탄화

중첩 배열을 평탄화하는 함수를 작성하십시오. 단순화를 위해 한 수준의 중첩만 있다고 가정하십시오.

설명: 한 수준의 중첩의 경우 스프레드 연산자 또는 flat() 메서드(ES2019)와 함께 Array.prototype.concat()을 사용할 수 있습니다.

function flattenArray(arr) { // flat() 사용 (ES2019+가 괜찮다면 더 간단함) // return arr.flat(); // reduce 및 concat 사용 return arr.reduce((acc, val) => acc.concat(val), []); } console.log(flattenArray([1, [2, 3], 4, [5]])); // [1, 2, 3, 4, 5]

문자열에서 모음 개수 세기

주어진 문자열에서 모음(a, e, i, o, u)의 개수를 세는 함수를 작성하십시오.

설명: 문자열을 반복하면서(대소문자 구분 없이) 각 문자가 모음인지 확인합니다. 카운터를 유지합니다.

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

문장을 제목 대문자로 변환

문장을 제목 대문자로 변환하는 함수를 작성하십시오(각 단어의 첫 글자 대문자).

설명: 문장을 단어로 분할한 다음, 각 단어를 반복하면서 첫 글자를 대문자로 만들고 나머지는 소문자로 만듭니다. 마지막으로 단어를 다시 결합합니다.

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) 문제

정수 배열 nums와 정수 target이 주어졌을 때, 합이 target이 되는 두 숫자의 인덱스를 반환하십시오.

설명: 일반적인 접근 방식은 반복하면서 숫자와 해당 인덱스를 해시 맵(또는 자바스크립트 객체)에 저장하는 것입니다. 각 숫자에 대해 target - 현재_숫자가 맵에 존재하는지 확인합니다.

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 []; // 또는 null, 또는 해결책이 없으면 오류 발생 } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

클로저를 사용한 카운터 구현

카운터 함수를 반환하는 함수를 생성하십시오. 반환된 함수가 호출될 때마다 카운트를 증가시키고 반환해야 합니다.

설명: 이것은 클로저를 보여줍니다. 내부 함수는 외부 함수가 실행을 마친 후에도 외부 함수의 스코프에 있는 count 변수에 접근할 수 있습니다.

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

첫 번째 반복되지 않는 문자 찾기

문자열에서 첫 번째 반복되지 않는 문자를 찾는 함수를 작성하십시오.

설명: 해시 맵을 사용하여 문자 빈도를 계산할 수 있습니다. 먼저, 문자열을 반복하여 빈도 맵을 만듭니다. 그런 다음, 문자열을 다시 반복하고 개수가 1인 첫 번째 문자를 반환합니다.

function firstNonRepeatingChar(str) { const charCount = {}; for (const char of str) { charCount[char] = (charCount[char] || 0) + 1; } for (const char of str) { if (charCount[char] === 1) { return char; } } return null; // 또는 모두 반복되는 경우를 위한 어떤 지표 } console.log(firstNonRepeatingChar('aabbcdeeff')); // 'c' console.log(firstNonRepeatingChar('swiss')); // 'w'

배열 청킹

배열을 지정된 크기의 그룹으로 분할하는 함수를 작성하십시오.

설명: 배열을 반복하면서 지정된 크기의 슬라이스를 가져와 새 배열에 푸시합니다. 마지막 청크가 더 작을 수 있는 경우를 처리합니다.

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

숫자가 소수인지 확인

주어진 숫자가 소수인지 확인하는 함수를 작성하십시오.

설명: 소수는 1보다 큰 자연수로, 1과 자기 자신 외에는 양의 약수가 없습니다. 2부터 숫자의 제곱근까지 반복하여 나누어 떨어지는지 확인합니다. 1과 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

문자열에서 가장 긴 단어 찾기

문장에서 가장 긴 단어를 찾는 함수를 작성하십시오.

설명: 문자열을 단어 배열로 분할합니다. 그런 다음, 배열을 반복하면서 지금까지 찾은 가장 긴 단어(또는 그 길이)를 추적합니다.

function findLongestWord(str) { const words = str.split(' '); let longestWord = ''; for (const word of words) { // 필요한 경우 단어 정리 (예: 구두점 제거) 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` 구현

Array.prototype.map 함수의 자체 버전을 구현하십시오.

설명: map 함수는 콜백을 받아들이고 원본 배열의 각 요소에 콜백을 적용하여 새 배열을 만듭니다. 함수는 배열을 반복하고 새 배열을 구축해야 합니다.

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` 구현

Array.prototype.filter 함수의 자체 버전을 구현하십시오.

설명: filter 함수는 콜백을 받아들이고 제공된 콜백 함수에 의해 구현된 테스트를 통과하는 모든 요소로 새 배열을 만듭니다.

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` 구현

Array.prototype.reduce 함수의 자체 버전을 구현하십시오.

설명: reduce 함수는 배열의 각 요소에 사용자 제공 '리듀서' 콜백 함수를 실행하고, 이전 요소에 대한 계산에서 반환된 값을 전달합니다. 배열의 모든 요소를 가로질러 리듀서를 실행한 최종 결과는 단일 값입니다. 초기값 인수를 처리하십시오.

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

메모이제이션 - 피보나치 수열

성능 최적화를 위해 메모이제이션을 사용하여 피보나치 함수를 구현하십시오.

설명: 피보나치 수열은 반복적인 계산을 포함합니다. 메모이제이션은 비용이 많이 드는 함수 호출의 결과를 저장하고 동일한 입력이 다시 발생할 때 캐시된 결과를 반환합니다.

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 (메모이제이션되지 않은 것보다 훨씬 빠름)

균형 잡힌 괄호 확인

괄호 {}[]()를 포함하는 문자열이 균형 잡혀 있는지 확인하는 함수를 작성하십시오.

설명: 스택을 사용하십시오. 여는 괄호를 만나면 스택에 푸시합니다. 닫는 괄호를 만나면 스택이 비어 있는지 또는 스택의 맨 위 요소가 일치하는 여는 괄호인지 확인합니다. 일치하면 스택을 팝합니다. 그렇지 않거나 스택이 비어 있으면 불균형입니다. 마지막에 스택이 비어 있으면 균형 잡힌 것입니다.

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

간단한 큐 구현

enqueue(뒤에 추가) 및 dequeue(앞에서 제거) 메서드를 사용하여 큐 데이터 구조를 구현하십시오.

설명: 큐는 FIFO(First-In, First-Out) 원칙을 따릅니다. 배열을 사용하여 enqueue에는 push, dequeue에는 shift를 사용할 수 있습니다.

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

간단한 스택 구현

push(맨 위에 추가) 및 pop(맨 위에서 제거) 메서드를 사용하여 스택 데이터 구조를 구현하십시오.

설명: 스택은 LIFO(Last-In, First-Out) 원칙을 따릅니다. 배열을 사용하여 pushpop 메서드를 사용할 수 있습니다.

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

수열에서 누락된 숫자 찾기

0, 1, 2, ..., n에서 가져온 n개의 고유한 숫자를 포함하는 배열이 주어졌을 때, 배열에서 누락된 숫자를 찾으십시오.

설명: 0부터 n까지의 숫자의 합은 n*(n+1)/2 공식을 사용하여 계산할 수 있습니다. 배열 요소의 실제 합계를 계산할 수 있습니다. 이 두 합계의 차이가 누락된 숫자가 됩니다.

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

디바운스 함수

디바운스 함수를 구현하십시오. 디바운싱은 함수가 호출되지 않은 채 특정 시간이 지날 때까지 다시 호출되지 않도록 합니다.

설명: setTimeoutclearTimeout을 사용하십시오. 디바운스된 함수가 호출될 때마다 이전 타임아웃을 지우고 새 타임아웃을 설정합니다. 실제 함수 호출은 타임아웃이 완료될 때만 발생합니다.

function debounce(func, delay) { let timeoutId; return function(...args) { clearTimeout(timeoutId); timeoutId = setTimeout(() => { func.apply(this, args); }, delay); }; } // 사용 예시: const sayHello = () => console.log('Hello!'); const debouncedHello = debounce(sayHello, 1000); debouncedHello(); // 1초 후 호출 (다시 호출되지 않으면) debouncedHello(); // 타이머 재설정 debouncedHello(); // 타이머 재설정, 이 호출 후 1초 후에 실제로 실행됩니다.

쓰로틀 함수

쓰로틀 함수를 구현하십시오. 쓰로틀링은 함수가 지정된 시간 간격 내에 최대 한 번만 호출되도록 합니다.

설명: 호출이 허용되는지 여부를 나타내는 플래그를 사용하십시오. 호출될 때 허용되면 함수를 실행하고, 플래그를 false로 설정하고, setTimeout을 사용하여 간격 후에 플래그를 재설정합니다.

function throttle(func, limit) { let inThrottle = false; return function(...args) { if (!inThrottle) { func.apply(this, args); inThrottle = true; setTimeout(() => inThrottle = false, limit); } }; } // 사용 예시: const logScroll = () => console.log('Scrolling...'); const throttledScroll = throttle(logScroll, 1000); // window.addEventListener('scroll', throttledScroll); // 1초에 한 번만 기록됩니다.

커링 함수

두 인수를 받는 함수를 받아 커링된 버전을 반환하는 함수를 작성하십시오.

설명: 커링은 여러 인수를 가진 함수를 각 인수를 하나씩 받는 일련의 함수로 변환합니다. f(a, b)f(a)(b)가 됩니다.

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

객체 깊은 복사(Deep Clone)

자바스크립트 객체의 깊은 복사를 수행하는 함수를 작성하십시오.

설명: 얕은 복사는 최상위 속성만 복사합니다. 깊은 복사는 중첩된 객체와 배열을 포함하여 모든 속성을 재귀적으로 복사합니다. 간단한 방법(제한 사항 있음, 예: 함수, 날짜, 정규식 등은 잘 처리하지 못함)은 JSON.parse(JSON.stringify(obj))를 사용하는 것입니다. 재귀적 접근 방식이 더 견고합니다.

function deepClone(obj) { // 간단한 버전 (제한 사항 있음) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("복사 실패:", e); return null; } // 더 견고한 재귀적 (기본 예시): /* 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 (깊은 복사임을 증명) console.log(cloned.b.c); // 3

객체 키 가져오기

객체에서 키 배열을 가져오는 방법은 무엇입니까?

설명: Object.keys(obj)를 사용하십시오.

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

객체 값 가져오기

객체에서 값 배열을 가져오는 방법은 무엇입니까?

설명: Object.values(obj)를 사용하십시오.

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

배열에 값 포함 여부 확인

배열에 특정 값이 포함되어 있는지 확인하는 방법은 무엇입니까?

설명: Array.prototype.includes(value)를 사용하십시오.

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

두 배열 병합

두 배열을 하나로 병합하는 방법은 무엇입까?

설명: 스프레드 문법(...) 또는 Array.prototype.concat()을 사용하십시오.

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

전역 스코프에서 'this' 설명

브라우저의 전역 스코프에서 this는 무엇을 참조합니까?

설명: 전역 실행 컨텍스트(어떤 함수 외부)에서 this는 전역 객체를 참조하며, 웹 브라우저에서는 window입니다.

console.log(this === window); // true (브라우저 환경에서)

객체 메서드에서 'this' 설명

객체 메서드 내에서 사용될 때 this는 무엇을 참조합니까?

설명: 함수가 객체의 메서드로 호출될 때, this는 메서드가 호출되는 객체를 참조합니다.

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

화살표 함수로 'this' 설명

화살표 함수는 this를 어떻게 처리합니까?

설명: 화살표 함수는 자체적인 this 컨텍스트를 갖지 않습니다. 대신, 정의된 주변(어휘적) 스코프에서 this를 상속받습니다.

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // 'this'가 상속되므로 42를 로깅합니다. }, 100); } new MyClass();

`let`, `const`, `var`의 차이점

let, const, var의 주요 차이점은 무엇입니까?

설명: var는 함수 스코프이며 호이스팅됩니다. letconst는 블록 스코프이며 호이스팅되지만 선언될 때까지 '시간적 사각지대'에 있습니다. const는 초기화되어야 하며 재할당될 수 없습니다.

function scopeTest() { var a = 1; let b = 2; const c = 3; if (true) { var a = 10; // 바깥 'a'를 재선언하고 영향을 줍니다. let b = 20; // 블록 내의 새로운 'b' // const c = 30; // 새로운 'c'가 될 것입니다. console.log(a, b, c); // 10, 20, 3 } console.log(a, b, c); // 10, 2, 3 } scopeTest();

Promise란 무엇입니까?

자바스크립트에서 Promise란 무엇인지 설명하십시오.

설명: Promise는 비동기 작업의 최종 완료(또는 실패) 및 그 결과 값을 나타내는 객체입니다. pending, fulfilled, rejected의 세 가지 상태 중 하나에 있을 수 있습니다.

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

`async/await` 사용

asyncawait는 Promise 작업을 어떻게 간소화합니까?

설명: async/await는 Promise에 대한 문법적 설탕을 제공하여 비동기 코드를 동기 코드처럼 보이게 하고 동작하게 합니다. async 함수는 항상 Promise를 반환하고, await는 Promise가 해결될 때까지 실행을 일시 중지합니다.

function delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } async function run() { console.log('시작 중...'); await delay(1000); console.log('1초 대기했습니다.'); await delay(500); console.log('추가로 0.5초 대기했습니다.'); return '완료!'; } run().then(console.log);

문자열을 숫자로 변환

문자열을 숫자로 변환하는 방법은 무엇입니까?

설명: parseInt(), parseFloat(), 또는 단항 더하기 연산자(+)를 사용하십시오.

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

숫자를 문자열로 변환

숫자를 문자열로 변환하는 방법은 무엇입까?

설명: String(), number.toString(), 또는 문자열 연결을 사용하십시오.

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

`JSON.stringify`는 무엇입니까?

JSON.stringify는 무엇을 합니까?

설명: 자바스크립트 객체 또는 값을 JSON 문자열로 변환합니다.

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

`JSON.parse`는 무엇입니까?

JSON.parse는 무엇을 합니까?

설명: JSON 문자열을 파싱하여 문자열로 설명된 자바스크립트 값 또는 객체를 구성합니다.

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

배열에서 스프레드 연산자

배열에서 스프레드 연산자는 어떻게 사용됩니까?

설명: 이터러블(배열과 같은)이 0개 이상의 인수 또는 요소가 예상되는 위치에서 확장될 수 있도록 합니다. 복사, 병합, 요소 추가에 유용합니다.

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

객체에서 스프레드 연산자

객체에서 스프레드 연산자는 어떻게 사용됩니까?

설명: 객체 속성을 복사하고 병합할 수 있도록 합니다.

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 }

배열 구조 분해 할당

예시와 함께 배열 구조 분해 할당을 설명하십시오.

설명: 배열에서 값을 개별 변수로 풀 수 있게 해주는 구문입니다.

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

객체 구조 분해 할당

예시와 함께 객체 구조 분해 할당을 설명하십시오.

설명: 객체에서 속성을 개별 변수로 풀 수 있게 해줍니다.

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` 구현

Function.prototype.call의 기본 버전을 어떻게 구현할 수 있습니까?

설명: call은 지정된 this 값과 개별적으로 제공된 인수로 함수를 호출합니다. 함수를 this 컨텍스트에 첨부하고, 호출한 다음, 제거할 수 있습니다.

Function.prototype.myCall = function(context, ...args) { context = context || window; const uniqueId = Symbol(); // 고유 키 사용 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` 구현

Function.prototype.apply의 기본 버전을 어떻게 구현할 수 있습니까?

설명: applycall과 유사하지만, 인수를 배열로 받습니다.

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.

이벤트 루프 설명

JavaScript 이벤트 루프를 간략하게 설명하세요.

설명: JavaScript는 단일 스레드이지만, 이벤트 루프를 사용하여 동시성을 달성합니다. 호출 스택은 동기 코드를 처리합니다. Web API는 비동기 작업(예: setTimeout, fetch)을 처리합니다. 비동기 작업이 완료되면 해당 콜백은 콜백 큐(또는 Promise의 경우 마이크로태스크 큐)로 이동합니다. 이벤트 루프는 호출 스택이 비어 있는지 지속적으로 확인하며, 비어 있으면 큐에서 다음 콜백을 스택으로 이동하여 실행합니다.

console.log('Start'); setTimeout(() => { console.log('Timeout Callback'); // 콜백 큐로 이동 }, 0); Promise.resolve().then(() => { console.log('Promise Resolved'); // 마이크로태스크 큐로 이동 }); console.log('End'); // 출력 순서: Start, End, Promise Resolved, Timeout Callback // (마이크로태스크는 매크로태스크/콜백보다 먼저 실행됩니다)

이진 탐색

정렬된 배열에 대한 이진 탐색 함수를 구현하세요.

설명: 이진 탐색은 탐색 간격을 반복적으로 절반으로 나누어 정렬된 배열에서 항목을 효율적으로 찾습니다. 탐색 키의 값이 간격 중간의 항목보다 작으면 간격을 아래쪽 절반으로 좁힙니다. 그렇지 않으면 위쪽 절반으로 좁힙니다. 값이 찾아지거나 간격이 비워질 때까지 이 작업을 수행합니다.

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; // 찾음 } else if (sortedArray[middle] < key) { start = middle + 1; // 오른쪽 절반 탐색 } else { end = middle - 1; // 왼쪽 절반 탐색 } } return -1; // 찾지 못함 } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

병합 정렬

병합 정렬 알고리즘을 구현하세요.

설명: 병합 정렬은 분할 정복 알고리즘입니다. 입력 배열을 두 개의 절반으로 나누고, 두 절반에 대해 자신을 호출한 다음, 두 정렬된 절반을 병합합니다. 병합 함수는 두 개의 정렬된 하위 배열을 하나의 정렬된 배열로 결합하는 데 중요합니다.

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]

퀵 정렬

퀵 정렬 알고리즘을 구현하세요.

설명: 퀵 정렬 또한 분할 정복 알고리즘입니다. 피벗으로 요소를 선택하고, 선택된 피벗을 중심으로 주어진 배열을 분할합니다. 피벗보다 작은 요소는 왼쪽으로, 큰 요소는 오른쪽으로 이동합니다. 그런 다음 재귀적으로 하위 배열을 정렬합니다.

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]

버블 정렬

버블 정렬 알고리즘을 구현하세요.

설명: 버블 정렬은 인접한 요소를 비교하고 잘못된 순서인 경우 교환하는 작업을 반복적으로 수행하는 간단한 정렬 알고리즘입니다. 목록을 정렬할 때까지 목록을 통한 통과가 반복됩니다.

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]]; // 교환 swapped = true; } } n--; // 최적화: 마지막 요소는 이미 제자리에 있습니다 } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

반복되지 않는 가장 긴 부분 문자열

문자열이 주어지면 반복되지 않는 가장 긴 부분 문자열의 길이를 찾으세요.

설명: 슬라이딩 윈도우 기술을 사용하세요. 윈도우(부분 문자열)와 해당 윈도우에 있는 문자 집합을 유지하세요. 오른쪽 포인터를 이동하여 윈도우를 확장하세요. 반복되는 문자가 발견되면 반복되는 문자가 제거될 때까지 왼쪽에서 윈도우를 축소하세요.

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

연결 리스트 구현

add 및 print 메서드가 있는 단일 연결 리스트를 구현하세요.

설명: 연결 리스트는 요소가 연속된 메모리 위치에 저장되지 않는 선형 데이터 구조입니다. 각 요소(노드)는 다음 요소를 가리킵니다. 헤드를 관리하고 새 노드를 추가하려면 Node 클래스와 LinkedList 클래스가 필요합니다.

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

이진 탐색 트리 (BST) 구현

insert 및 find 메서드가 있는 이진 탐색 트리를 구현하세요.

설명: BST는 다음 속성을 가진 노드 기반 이진 트리 데이터 구조입니다. 노드의 왼쪽 하위 트리는 노드의 키보다 작은 키를 가진 노드만 포함합니다. 노드의 오른쪽 하위 트리는 노드의 키보다 큰 키를 가진 노드만 포함합니다. 두 하위 트리 모두 이진 탐색 트리여야 합니다.

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

배열 회전

배열을 k 단계만큼 오른쪽으로 회전시키는 함수를 작성하세요.

설명: 배열을 회전시킨다는 것은 요소를 이동시키는 것을 의미합니다. 오른쪽 회전의 경우 마지막 요소가 첫 번째 요소가 됩니다. 이 작업을 k번 반복하는 것은 비효율적입니다. 더 나은 방법은 배열 조작을 사용하거나 새 위치를 계산하는 것입니다.

function rotateArray(nums, k) { k = k % nums.length; // k > length인 경우 처리 if (k === 0) return nums; const partToMove = nums.splice(nums.length - k); nums.unshift(...partToMove); return nums; } console.log(rotateArray([1, 2, 3, 4, 5, 6, 7], 3)); // [5, 6, 7, 1, 2, 3, 4]

두 배열의 교집합 찾기

두 배열이 주어지면 두 배열의 교집합(두 배열에 공통으로 있는 요소)을 계산하는 함수를 작성하세요.

설명: 한 배열을 Set으로 변환하여 평균 시간 O(1) 조회 속도를 얻는 것이 좋습니다. 그런 다음 두 번째 배열을 반복하고 각 요소가 Set에 있는지 확인합니다. 일치하는 요소를 수집합니다.

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]

아나그램 그룹화

문자열 배열이 주어지면 아나그램을 함께 그룹화하세요.

설명: 핵심은 각 아나그램 그룹에 대한 고유한 서명을 찾는 것입니다. 일반적인 방법은 각 단어를 알파벳순으로 정렬하는 것입니다. 동일하게 정렬된 문자열이 되는 단어는 아나그램입니다. 키는 정렬된 단어이고 값은 원본 단어의 배열인 해시 맵을 사용하세요.

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

0을 끝으로 이동

nums 배열이 주어지면, 0이 아닌 요소의 상대적인 순서를 유지하면서 모든 0을 끝으로 이동시키는 함수를 작성하세요.

설명: '두 포인터' 또는 '스노우볼' 접근 방식을 사용하세요. 한 포인터는 다음 0이 아닌 요소가 들어갈 위치를 추적합니다. 배열을 반복하여 요소가 0이 아니면 포인터 위치에 놓고 포인터를 증가시킵니다. 마지막으로 나머지를 0으로 채웁니다.

function moveZeroes(nums) { let nonZeroIndex = 0; // 모든 0이 아닌 요소를 앞으로 이동 for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // 나머지를 0으로 채움 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]

최대 부분 배열 (Kadane's Algorithm)

정수 배열 nums가 주어지면, 가장 큰 합을 가지는 연속적인 부분 배열(최소 하나의 숫자를 포함)을 찾아 그 합을 반환하세요.

설명: Kadane의 알고리즘은 이 문제를 해결하는 효율적인 방법입니다. 배열을 반복하면서 현재 위치에서 끝나는 currentMax 합과 지금까지 찾은 globalMax 합을 추적합니다. currentMax가 음수가 되면 0으로 재설정합니다(또는 현재 요소로).

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

문자열의 순열

주어진 문자열의 모든 순열을 생성하는 함수를 작성하세요.

설명: 이것은 일반적으로 재귀와 백트래킹을 사용하여 해결됩니다. 각 문자에 대해 고정하고 나머지 문자열에 대한 순열을 재귀적으로 생성합니다. 기본 사례: 문자열에 하나의 문자만 있으면 해당 문자를 반환합니다.

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)]; // 필요한 경우 중복 문자 처리를 위해 Set 사용 } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

최대 공약수 (GCD)

두 숫자의 최대 공약수 (GCD)를 찾는 함수를 작성하세요.

설명: 유클리드 알고리즘은 효율적인 방법입니다. b가 0이면 a가 GCD입니다. 그렇지 않으면 a와 b의 GCD는 b와 a % b (a를 b로 나눈 나머지)의 GCD와 같습니다.

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

최소 공배수 (LCM)

두 숫자의 최소 공배수 (LCM)를 찾는 함수를 작성하세요.

설명: LCM은 GCD를 사용하여 다음 공식으로 계산할 수 있습니다: LCM(a, b) = |a * b| / GCD(a, b).

function gcd(a, b) { // 이전 문제의 도우미 함수 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 구현

Promise.all과 유사하게 동작하는 함수를 구현하세요.

설명: Promise.all은 프로미스 배열을 받아 모든 입력 프로미스가 해결될 때 해결되거나 입력 프로미스 중 하나라도 거부되면 거부되는 단일 프로미스를 반환합니다. 해결된 값은 해결된 값의 배열입니다.

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); // 오류 발생 시 즉시 거부 }); }); } // 사용 예시: 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']

이진 트리 뒤집기

이진 트리를 뒤집는 함수를 작성하세요.

설명: 이진 트리를 뒤집으려면 모든 노드에 대해 왼쪽 및 오른쪽 자식을 바꿉니다. 이는 재귀적으로 또는 반복적으로 (큐 또는 스택을 사용하여) 수행할 수 있습니다.

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; } // 자식 교환 [root.left, root.right] = [root.right, root.left]; // 왼쪽 및 오른쪽 자식에 대해 재귀 호출 invertTree(root.left); invertTree(root.right); return root; } // 예시: 4 -> [2, 7] -> [1, 3, 6, 9] // 다음으로 변경: 4 -> [7, 2] -> [9, 6, 3, 1]

이진 트리의 최대 깊이

이진 트리가 주어지면 최대 깊이를 찾으세요.

설명: 최대 깊이는 루트 노드에서 가장 먼 리프 노드까지의 가장 긴 경로를 따라 있는 노드의 수입니다. 이는 재귀적으로 찾을 수 있습니다: MaxDepth = 1 + max(MaxDepth(left), MaxDepth(right)). 기본 사례는 노드가 null일 때 깊이가 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; } // 예시: 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

주식을 사고 파는 가장 좋은 시점

prices 배열이 주어지는데, prices[i]는 i번째 날의 특정 주식 가격입니다. 한 주식을 매수할 날을 선택하고, 미래의 다른 날에 그 주식을 매도하여 최대 이익을 얻으려고 합니다. 최대 이익을 반환하세요.

설명: 가격을 반복하면서 지금까지 만난 최소 가격(minPrice)과 지금까지 찾은 최대 이익(maxProfit)을 추적합니다. 매일, 오늘 팔았다면 얻을 수 있는 잠재적 이익(현재 가격 - minPrice)을 계산하고, 더 높으면 maxProfit을 업데이트합니다.

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 (1에서 사고 6에서 팜) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (이익을 얻을 수 없음)

단일 숫자

비어 있지 않은 정수 배열 nums가 주어지면, 모든 요소는 두 번 나타나지만 하나만 제외합니다. 그 단일 요소를 찾으세요.

설명: 이 문제를 해결하는 매우 효율적인 방법은 XOR 비트 연산자(^)를 사용하는 것입니다. 자신과 XOR하면 0이 됩니다. 0과 XOR하면 그 숫자 자체가 됩니다. 배열의 모든 숫자를 XOR하면 쌍은 상쇄되어(0이 됨) 단일 숫자만 남습니다.

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

과반수 요소

크기가 n인 nums 배열이 주어지면 과반수 요소를 반환하세요. 과반수 요소는 ⌊n / 2⌋번 이상 나타나는 요소입니다.

설명: Boyer-Moore 투표 알고리즘은 효율적인 방법입니다. candidate와 count를 초기화합니다. 배열을 반복합니다. count가 0이면 현재 요소를 candidate로 설정합니다. 현재 요소가 candidate와 일치하면 count를 증가시키고, 그렇지 않으면 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

계단 오르기

계단을 오르고 있습니다. 정상에 도달하려면 n개의 계단이 필요합니다. 매번 1계단 또는 2계단을 오를 수 있습니다. 정상에 오르는 방법은 몇 가지 고유한 방법이 있습니까?

설명: 이것은 고전적인 동적 프로그래밍 문제이며, 피보나치 수열과 매우 유사합니다. n번째 계단에 도달하는 방법의 수는 n-1번째 계단에 도달하는 방법(1계단 오르기)과 n-2번째 계단에 도달하는 방법(2계단 오르기)의 합입니다. 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

자신을 제외한 배열의 곱

정수 배열 nums가 주어지면, answer[i]가 nums[i]를 제외한 nums의 모든 요소의 곱과 같도록 answer 배열을 반환하세요. 나눗셈 연산자를 사용하지 마세요.

설명: 이것은 접두사 곱과 접미사 곱을 계산하여 해결할 수 있습니다. 인덱스 i에서의 결과는 i 이전의 모든 요소의 곱에 i 이후의 모든 요소의 곱을 곱한 것입니다.

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // 접두사 곱 계산 let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // 접미사 곱 계산 및 접두사 곱과 곱함 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]

섬의 개수

'1'(육지)과 '0'(물)으로 이루어진 2D 그리드 맵이 주어지면 섬의 개수를 세세요. 섬은 물로 둘러싸여 있으며, 인접한 육지를 수평 또는 수직으로 연결하여 형성됩니다.

설명: 그리드의 각 셀을 반복합니다. '1'(육지)을 찾으면 섬의 개수를 증가시키고 해당 셀에서 깊이 우선 탐색(DFS) 또는 너비 우선 탐색(BFS)을 시작합니다. 탐색하는 동안 연결된 모든 '1'을 '0'(또는 방문됨)으로 표시하여 다시 세지 않도록 합니다.

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'; // 방문됨으로 표시 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 (복사본에서 실행하거나 그리드를 복원해야 합니다)

LRU 캐시

최근 사용되지 않은(LRU) 캐시를 위한 데이터 구조를 설계하고 구현하세요. get 및 put 작업을 지원해야 합니다.

설명: LRU 캐시는 용량에 도달하면 가장 최근에 사용되지 않은 항목을 제거합니다. 일반적인 구현은 O(1) 조회 속도를 위한 Map(또는 해시 맵)과 '최근 사용' 상태를 O(1) 업데이트하기 위한 이중 연결 리스트를 사용합니다. JavaScript의 Map은 삽입 순서를 유지하여 기본적인 구현을 단순화할 수 있습니다.

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); // '끝'(가장 최근)에 다시 삽입하기 위해 제거 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); // 2를 제거 console.log(cache.get(2)); // -1

괄호 생성

n쌍의 괄호가 주어지면, 올바르게 형성된 괄호의 모든 조합을 생성하는 함수를 작성하세요.

설명: 이것은 고전적인 백트래킹 문제입니다. 열린 괄호와 닫힌 괄호의 개수를 유지합니다. open < n이면 열린 괄호를 추가할 수 있습니다. close < open이면 닫힌 괄호를 추가할 수 있습니다. 기본 사례는 문자열 길이가 2 * n에 도달할 때입니다.

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

가장 많은 물을 담을 수 있는 컨테이너

n개의 음이 아닌 정수 a1, a2, ..., an이 주어지는데, 각각 좌표 (i, ai)의 점을 나타냅니다. n개의 수직선이 선 i의 두 끝점이 (i, ai) 및 (i, 0)이 되도록 그려집니다. 두 선과 x축이 함께 컨테이너를 형성하여 컨테이너가 가장 많은 물을 담을 수 있도록 하는 두 선을 찾으세요.

설명: 투 포인터 접근 방식을 사용하세요. 한 포인터는 시작점에, 다른 포인터는 끝점에 놓습니다. 면적을 계산합니다. 면적은 더 짧은 선에 의해 제한됩니다. 더 큰 면적을 찾기 위해 더 짧은 선을 가리키는 포인터를 안쪽으로 이동시킵니다.

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

n개의 정수로 구성된 nums 배열이 주어지면, nums에 a + b + c = 0이 되는 요소 a, b, c가 있습니까? 합이 0이 되는 배열의 모든 고유한 세 쌍을 찾으세요.

설명: 먼저 배열을 정렬합니다. 그런 다음 하나의 포인터 i로 배열을 반복합니다. 각 i에 대해, -nums[i]로 합산되는 두 숫자를 찾기 위해 두 개의 포인터(left는 i+1에서 시작, right는 끝에서 시작)를 더 사용합니다. 고유한 세 쌍을 보장하기 위해 중복을 처리합니다.

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; // 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++; // 중복 건너뛰기 while (left < right && nums[right] === nums[right - 1]) right--; // 중복 건너뛰기 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]]

검색 삽입 위치

정렬된 고유한 정수 배열과 대상 값이 주어지면, 대상이 발견되면 인덱스를 반환합니다. 그렇지 않으면 순서대로 삽입되었을 때의 인덱스를 반환합니다.

설명: 이것은 이진 탐색의 변형입니다. 이진 탐색을 수행하지만 요소가 발견되지 않으면 start 포인터가 요소가 삽입되어야 하는 위치에 있게 됩니다.

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; // 찾지 못하면 start는 삽입 지점입니다 } 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

두 개의 정렬된 리스트 병합 (연결 리스트)

두 개의 정렬된 연결 리스트를 병합하고 새 정렬된 리스트로 반환합니다. 새 리스트는 처음 두 리스트의 노드를 이어붙여 만들어야 합니다.

설명: 더미 헤드 노드를 사용하여 프로세스를 단순화합니다. 새 리스트를 만들기 위해 포인터를 사용합니다. 두 입력 리스트의 현재 노드를 비교하고 더 작은 노드를 새 리스트에 추가한 다음 해당 리스트의 포인터를 이동시킵니다. 한 리스트가 소진될 때까지 반복한 다음 다른 리스트의 나머지를 추가합니다.

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; } // 예시: 1->2->4 와 1->3->4 => 1->1->2->3->4->4

Merge Two Sorted Lists (Linked Lists)

두 개의 정렬된 연결 리스트를 병합하여 새로운 정렬된 리스트로 반환합니다. 새 리스트는 첫 번째 두 리스트의 노드를 함께 연결하여 만들어져야 합니다.

설명: 더미 헤드 노드를 사용하여 프로세스를 단순화하십시오. 포인터를 사용하여 새 목록을 구축하십시오. 두 입력 리스트의 현재 노드를 비교하고 더 작은 노드를 새 리스트에 추가한 다음 해당 리스트의 포인터를 진행합니다. 한 리스트가 소진될 때까지 반복한 다음 다른 리스트의 나머지를 추가합니다.

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

Symmetric Tree

이진 트리가 자체적으로 미러인지 (즉, 중앙을 기준으로 대칭인지) 확인합니다.

설명: 이것은 재귀적으로 해결할 수 있습니다. 트리는 왼쪽 서브트리가 오른쪽 서브트리의 미러 이미지인 경우 대칭입니다. 두 트리가 미러인지 확인하는 헬퍼 함수 isMirror(t1, t2)를 만듭니다. 이 함수는 다음을 확인해야 합니다: 1. t1.val === t2.val. 2. t1.left가 t2.right의 미러인지. 3. t1.right가 t2.left의 미러인지.

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

Level Order Traversal (BST/Tree)

이진 트리가 주어지면 노드 값의 레벨 순서 순회 (즉, 왼쪽에서 오른쪽으로, 레벨별로)를 반환합니다.

설명: 이것은 너비 우선 탐색(BFS)입니다. 큐를 사용합니다. 루트를 큐에 추가하는 것으로 시작합니다. 큐가 비어 있지 않은 동안 현재 레벨의 모든 노드를 처리합니다. 각 처리된 노드에 대해 자식 (존재하는 경우)을 다음 레벨을 위해 큐에 추가합니다.

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

Convert Sorted Array to Height-Balanced BST

요소가 오름차순으로 정렬된 배열이 주어지면, 이를 높이 균형 이진 탐색 트리(BST)로 변환합니다.

설명: 높이 균형 BST를 만들려면 배열의 중간 요소를 루트로 선택해야 합니다. 그런 다음 배열의 왼쪽 절반에서 왼쪽 서브트리를 재귀적으로 구축하고 오른쪽 절반에서 오른쪽 서브트리를 구축합니다.

class TreeNode { constructor(val = 0, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function sortedArrayToBST(nums) { if (!nums || nums.length === 0) return null; function buildBST(start, end) { if (start > end) return null; const mid = Math.floor((start + end) / 2); const node = new TreeNode(nums[mid]); node.left = buildBST(start, mid - 1); node.right = buildBST(mid + 1, end); return node; } return buildBST(0, nums.length - 1); } // Example: [-10, -3, 0, 5, 9] // Output: A tree like [0, -3, 9, -10, null, 5, null]

Implement `bind`

Function.prototype.bind의 자신만의 버전을 구현하십시오.

설명: bind는 새 함수를 생성합니다. 이 함수는 호출될 때 this 키워드가 제공된 값으로 설정되며, 새 함수가 호출될 때 제공되는 모든 인수보다 먼저 제공된 인수의 시퀀스를 가집니다. 반환된 함수 내에서 apply 또는 call을 사용하여 부분 적용(미리 설정된 인수)을 처리합니다.

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

Find the Kth Largest Element

정렬되지 않은 배열에서 k번째로 큰 요소를 찾습니다. k번째로 큰 요소는 정렬된 순서의 k번째 요소이며, k번째 고유 요소가 아님을 참고하십시오.

설명: 간단한 접근 방식은 배열을 정렬한 다음 n - k 인덱스의 요소를 선택하는 것입니다. 큰 배열의 경우 더 효율적인 접근 방식은 최소 힙 또는 퀵셀렉트와 같은 선택 알고리즘을 사용하는 것입니다.

function findKthLargest(nums, k) { // Simple approach: Sort nums.sort((a, b) => b - a); // Sort in descending order return nums[k - 1]; // Note: Quickselect would be more efficient in an interview // but sorting is easier to implement quickly. } console.log(findKthLargest([3, 2, 1, 5, 6, 4], 2)); // 5 console.log(findKthLargest([3, 2, 3, 1, 2, 4, 5, 5, 6], 4)); // 4

Check if Object has Property

객체가 특정 속성을 가지고 있는지 어떻게 확인할 수 있습니까?

설명: in 연산자(자체 및 상속된 속성 확인), Object.prototype.hasOwnProperty.call(obj, prop)(자체 속성만 확인) 또는 단순히 obj.prop !== undefined(undefined 값의 경우 까다로울 수 있음)를 사용할 수 있습니다.

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

Integer to Roman

정수를 로마 숫자 표현으로 변환하는 함수를 작성하십시오.

설명: 로마 숫자와 해당 값을 가장 큰 것부터 가장 작은 것 순서로 매핑을 생성합니다. 이 맵을 반복합니다. 각 값에 대해 가능한 한 여러 번 입력 숫자에서 빼고 매번 로마 숫자를 추가합니다.

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

Roman to Integer

로마 숫자를 정수로 변환하는 함수를 작성하십시오.

설명: 로마 기호를 값으로 매핑하는 맵을 만듭니다. 문자열을 반복합니다. 현재 기호의 값이 다음 기호의 값보다 작으면 ('IV' 또는 'IX'와 같이) 현재 값을 빼고, 그렇지 않으면 더합니다.

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

Implement a `Set`

add, has, delete 및 size 메서드를 사용하여 기본 Set 데이터 구조를 (내장 Set을 사용하지 않고) 구현하십시오.

설명: JavaScript 객체(해시 맵)를 기본 저장소로 사용할 수 있습니다. 키는 세트 요소가 됩니다(다른 유형을 저장하고 키로 고유성을 보장하는 방법을 처리해야 할 수도 있습니다).

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

Pascal's Triangle

정수 numRows가 주어지면, 파스칼 삼각형의 처음 numRows를 생성합니다.

설명: 파스칼 삼각형에서 각 숫자는 바로 위 두 숫자의 합입니다. [[1]]로 시작합니다. 각 후속 행에 대해 1로 시작하고 끝납니다. 각 중간 요소는 위 행의 동일한 인덱스와 이전 인덱스 요소의 합입니다.

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

Word Search

2D 보드와 단어가 주어지면, 그리드에 단어가 존재하는지 찾으십시오. 단어는 순차적으로 인접한 셀의 문자로 구성될 수 있으며, 여기서 '인접' 셀은 수평 또는 수직으로 인접한 셀을 의미합니다.

설명: 이것은 백트래킹을 사용하는 깊이 우선 탐색(DFS)이 필요합니다. 각 셀을 반복합니다. 셀이 단어의 첫 번째 문자와 일치하면 DFS를 시작합니다. DFS 함수는 이웃을 확인하여 다음 문자와 일치하고 현재 경로에서 방문되지 않았는지 확인합니다. 경로가 실패하면 셀의 표시를 해제하여 백트랙합니다.

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

Minimum Window Substring

두 문자열 s와 t가 주어지면, t의 모든 문자를 포함하는 s의 최소 창을 찾으십시오. s에 t의 모든 문자를 포함하는 창이 없으면 빈 문자열 을 반환하십시오.

설명: 두 개의 포인터(left와 right)와 해시 맵을 사용하여 슬라이딩 윈도우 접근 방식을 사용하십시오. 한 맵은 t에서 필요한 문자 수를 저장합니다. 다른 맵은 현재 창의 문자 수를 저장합니다. right를 사용하여 창을 확장합니다. 창에 필요한 모든 문자가 포함되면 left를 사용하여 창을 축소하여 가능한 최소 창을 찾으십시오.

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'

Reverse Linked List

단일 연결 리스트의 head가 주어지면, 리스트를 역순으로 만들고 역순 리스트를 반환합니다.

설명: 리스트를 반복하면서 각 노드의 next 포인터를 이전 노드를 가리키도록 변경해야 합니다. 반복하는 동안 previous, current, next 노드를 추적하십시오.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function reverseList(head) { let prev = null; let current = head; let next = null; while (current !== null) { next = current.next; // Store next node current.next = prev; // Reverse current node's pointer prev = current; // Move prev one step forward current = next; // Move current one step forward } return prev; // New head is the last 'prev' } // Example: 1->2->3->4->5 becomes 5->4->3->2->1

Detect Cycle in Linked List

연결 리스트의 head가 주어지면, 연결 리스트에 사이클이 있는지 확인합니다.

설명: 플로이드의 토끼와 거북이 알고리즘을 사용하십시오. 하나는 한 번에 한 단계씩 움직이는 포인터(slow)와 다른 하나는 한 번에 두 단계씩 움직이는 포인터(fast)를 사용합니다. 사이클이 있으면 fast 포인터가 결국 slow 포인터를 따라잡을 것입니다.

class ListNode { constructor(val = 0, next = null) { this.val = val; this.next = next; } } function hasCycle(head) { if (!head || !head.next) return false; let slow = head; let fast = head.next; while (slow !== fast) { if (!fast || !fast.next) return false; // Reached end, no cycle slow = slow.next; fast = fast.next.next; } return true; // Pointers met, cycle exists } // Example: 3->2->0->-4 with -4 pointing back to 2. hasCycle returns true.

Implement `Object.create`

Object.create(proto)의 동작을 모방하는 함수를 구현하십시오.

설명: Object.create는 새 객체를 생성하며, 기존 객체를 새로 생성된 객체의 프로토타입으로 사용합니다. 이를 위해 임시 생성자 함수를 만들고, 해당 프로토타입을 입력 proto로 설정한 다음, 새 인스턴스를 반환하여 이 작업을 수행할 수 있습니다.

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

What is Hoisting?

JavaScript에서 호이스팅을 설명하고 예를 제공하십시오.

설명: 호이스팅은 코드 실행 전에 선언(var)을 현재 스코프(전역 또는 함수)의 맨 위로 이동시키는 JavaScript의 기본 동작입니다. 변수 선언(var)은 호이스팅되어 undefined로 초기화됩니다. 함수 선언은 완전히 호이스팅됩니다(이름과 본문 모두). let과 const는 호이스팅되지만 초기화되지 않아 Temporal Dead Zone을 유발합니다.

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

Explain Event Bubbling and Capturing

DOM 컨텍스트에서 이벤트 버블링과 캡처링을 설명하십시오.

설명: 이들은 HTML DOM에서 이벤트 전파의 두 단계입니다. 캡처링 단계: 이벤트가 window에서 대상 요소로 아래로 이동합니다. 버블링 단계: 이벤트가 대상 요소에서 window로 다시 위로 이동합니다. 기본적으로 대부분의 이벤트 핸들러는 버블링 단계에서 등록됩니다. useCapture = true를 사용하여 addEventListener(type, listener, useCapture)를 사용하여 캡처링 단계에서 이벤트를 처리할 수 있습니다.

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

Implement `JSON.parse` manually (basic)

JSON.parse의 매우 기본적인 버전(간단한 객체, 배열, 문자열, 숫자 처리)을 직접 구현해 보십시오.

설명: 이는 전체적으로 매우 복잡한 작업이지만, 라이브 코딩 환경에서는 매우 단순화된 하위 집합을 처리하도록 요청받을 수 있습니다. 문자열을 구문 분석하고 객체 {} 및 배열 [] 경계, 키-값 쌍 (key: value) 및 기본 데이터 유형을 식별해야 합니다. eval 또는 new Function은 이것을 속일 수 있지만 위험합니다. 실제 파서는 렉서/토크나이저와 파서를 사용합니다.

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

Flatten a Deeply Nested Array

깊게 중첩된 배열을 평탄화하는 함수를 작성하십시오.

설명: 이전 평탄화(한 레벨)와 달리, 이것은 모든 중첩 레벨을 처리해야 합니다. 재귀는 자연스러운 적합입니다. 배열을 반복합니다. 요소가 배열이면 재귀적으로 flatten을 호출하고 결과를 연결합니다. 그렇지 않으면 요소를 추가합니다.

function deepFlatten(arr) { let flattened = []; arr.forEach(item => { if (Array.isArray(item)) { flattened = flattened.concat(deepFlatten(item)); } else { flattened.push(item); } }); return flattened; // ES2019+ provides a much simpler way: // return arr.flat(Infinity); } console.log(deepFlatten([1, [2, [3, 4], 5], 6])); // [1, 2, 3, 4, 5, 6]

Implement a Trie (Prefix Tree)

insert, search 및 startsWith 메서드를 사용하여 트라이(접두사 트리) 데이터 구조를 구현하십시오.

설명: 트라이는 문자열 데이터 세트에서 키를 효율적으로 저장하고 검색하는 데 사용되는 트리와 유사한 데이터 구조입니다. 각 노드는 문자를 나타내며, 루트에서부터의 경로는 단어 또는 접두사를 나타냅니다.

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

Shuffle an Array (Fisher-Yates)

피셔-예이츠(또는 Knuth) 알고리즘을 사용하여 배열을 제자리에서 섞는 함수를 작성하십시오.

설명: 피셔-예이츠 알고리즘은 배열을 마지막 요소부터 첫 번째 요소까지 반복합니다. 각 반복에서 현재 요소를 배열의 시작부터 현재 요소까지(포함) 무작위로 선택된 요소와 바꿉니다.

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

Compose Functions

여러 함수를 받아들이고, 오른쪽에서 왼쪽으로 적용하는 새 함수를 반환하는 compose 함수를 구현하십시오.

설명: 함수 합성 (f ∘ g)(x) = f(g(x))는 다른 함수의 결과에 하나의 함수를 적용합니다. 일반적인 compose(f, g, h)는 f(g(h(x)))를 의미합니다. reduceRight를 사용하여 우아하게 구현하십시오.

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

Pipe Functions

여러 함수를 받아들이고, 왼쪽에서 오른쪽으로 적용하는 새 함수를 반환하는 pipe 함수를 구현하십시오.

설명: compose와 유사하지만 적용 순서가 반대입니다: pipe(f, g, h)는 h(g(f(x)))를 의미합니다. reduce를 사용하여 구현하십시오.

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

Coin Change Problem

동전 액면가 배열과 금액이 주어지면, 해당 금액을 만드는 데 필요한 최소 동전 수를 찾으십시오. 각 동전은 무한정 공급된다고 가정합니다.

설명: 이것은 고전적인 동적 프로그래밍 문제입니다. dp[i]가 i 금액에 필요한 최소 동전 수를 저장하는 dp 배열을 만드십시오. dp[i] = min(dp[i - coin]) + 1 (모든 동전에 대해).

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

Lowest Common Ancestor (BST)

이진 탐색 트리(BST)가 주어지면, 주어진 두 노드의 가장 낮은 공통 조상(LCA)을 찾으십시오.

설명: LCA는 주어진 두 노드를 자손으로 갖는 가장 깊은 노드입니다. BST에서 루트에서부터 순회하여 찾을 수 있습니다. 두 노드 모두 현재 노드보다 작으면 왼쪽으로 이동합니다. 둘 다 크면 오른쪽으로 이동합니다. 하나는 작고 다른 하나는 크면 (또는 하나가 현재 노드이면) 현재 노드가 LCA입니다.

class Node { constructor(val) { this.val = val; this.left = this.right = null; } } function lowestCommonAncestor(root, p, q) { let current = root; while (current) { if (p.val > current.val && q.val > current.val) { current = current.right; } else if (p.val < current.val && q.val < current.val) { current = current.left; } else { return current; // Found LCA } } return null; // Should not happen in a valid BST with p and q } // Example: root = [6,2,8,0,4,7,9,null,null,3,5], p = 2, q = 8 => LCA is 6

Serialize and Deserialize Binary Tree

이진 트리를 직렬화하고 역직렬화하는 알고리즘을 설계하십시오.

설명: 직렬화는 트리를 문자열 또는 배열로 변환합니다. 역직렬화는 트리를 재구성합니다. 일반적인 방법은 전위 순회(DFS)입니다. 구조를 보존하기 위해 null 노드에 특수 마커(예: '#')를 사용하십시오.

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

Implement `setInterval` with `setTimeout`

setInterval을 모방하지만 setTimeout을 재귀적으로 사용하는 mySetInterval 함수를 구현하십시오.

설명: setInterval은 N 밀리초마다 함수를 반복적으로 실행합니다. 각 실행 후 setTimeout으로 함수가 자체적으로 호출되도록 하여 이를 달성할 수 있습니다. 또한 이를 지울 방법(myClearInterval)도 필요합니다.

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

Graph Traversal (BFS & DFS)

주어진 그래프(인접 리스트 표현)에 대해 너비 우선 탐색(BFS)과 깊이 우선 탐색(DFS)을 구현하십시오.

설명: BFS는 이웃을 먼저 탐색하고(큐 사용), DFS는 각 브랜치를 가능한 한 멀리 탐색합니다(스택 또는 재귀 사용). 사이클 및 중복 작업을 피하기 위해 방문한 노드를 추적하십시오.

const graph = { A: ['B', 'C'], B: ['D'], C: ['E'], D: [], E: ['F'], F: [] }; function bfs(graph, startNode) { const queue = [startNode]; const visited = new Set(); const result = []; visited.add(startNode); while (queue.length > 0) { const node = queue.shift(); result.push(node); for (const neighbor of graph[node]) { if (!visited.has(neighbor)) { visited.add(neighbor); queue.push(neighbor); } } } return result; } function dfs(graph, startNode) { const stack = [startNode]; const visited = new Set(); const result = []; while (stack.length > 0) { const node = stack.pop(); if (!visited.has(node)) { visited.add(node); result.push(node); // Add neighbors in reverse to process them in order later (optional) for (let i = graph[node].length - 1; i >= 0; i--) { stack.push(graph[node][i]); } } } return result; } console.log('BFS:', bfs(graph, 'A')); // ['A', 'B', 'C', 'D', 'E', 'F'] console.log('DFS:', dfs(graph, 'A')); // ['A', 'B', 'D', 'C', 'E', 'F']

Rotate Image (Matrix)

n x n 2D 행렬로 표현된 이미지가 주어집니다. 이미지를 시계 방향으로 90도 회전하십시오.

설명: 이를 달성하는 일반적인 방법은 먼저 행렬을 전치(matrix[i][j]와 matrix[j][i]를 교환)한 다음 각 행을 뒤집는 것입니다.

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

나선형 행렬 순회

m x n 행렬이 주어졌을 때, 모든 요소를 나선형 순서로 반환합니다.

설명: 네 개의 포인터 top, bottom, left, right를 사용하여 경계를 정의합니다. 위쪽 행을 순회한 다음 오른쪽 열, 아래쪽 행, 왼쪽 열을 순회하고 각 순회 후에 경계를 축소합니다. left가 right를 교차하거나 top이 bottom을 교차할 때까지 반복합니다.

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

행렬 0 설정

m x n 행렬이 주어졌을 때, 요소가 0이면 해당 행과 열 전체를 0으로 설정합니다. 제자리에서 수행합니다.

설명: 순진한 접근 방식은 O(m*n)의 추가 공간이 필요합니다. O(1) 공간(또는 O(m+n))으로 수행하려면 첫 번째 행과 첫 번째 열을 사용하여 어떤 행/열을 0으로 설정해야 하는지에 대한 정보를 저장할 수 있습니다. 첫 번째 행/열 자체를 0으로 설정해야 하는지에 대한 별도의 플래그가 필요합니다.

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

Promise.race 구현

Promise.race와 유사하게 작동하는 함수를 구현합니다.

설명: Promise.race는 프로미스 배열을 취하고 단일 프로미스를 반환합니다. 이 반환된 프로미스는 입력 프로미스 중 하나라도 처리되는 즉시(이행 또는 거부) 해당 프로미스의 값 또는 이유로 처리됩니다.

function myPromiseRace(promises) { return new Promise((resolve, reject) => { if (!promises || promises.length === 0) { return; // Or resolve/reject depending on spec } promises.forEach(promise => { Promise.resolve(promise).then(resolve, reject); }); }); } const p1 = new Promise((resolve) => setTimeout(resolve, 500, 'one')); const p2 = new Promise((resolve) => setTimeout(resolve, 100, 'two')); myPromiseRace([p1, p2]).then(value => console.log(value)); // 'two'

싱글톤 패턴

JavaScript에서 싱글톤 디자인 패턴을 구현합니다.

설명: 싱글톤 패턴은 클래스가 하나의 인스턴스만 갖도록 보장하고 이에 대한 전역 접근 지점을 제공합니다. 이는 클로저를 사용하여 인스턴스를 유지함으로써 달성할 수 있습니다.

const Singleton = (function() { let instance; function createInstance() { // Private methods and variables const privateVar = 'I am private'; function privateMethod() { console.log('Private'); } return { // Public methods and variables publicVar: 'I am public', publicMethod: function() { console.log('Public'); }, getPrivate: function() { return privateVar; } }; } return { getInstance: function() { if (!instance) { instance = createInstance(); } return instance; } }; })(); const instance1 = Singleton.getInstance(); const instance2 = Singleton.getInstance(); console.log(instance1 === instance2); // true console.log(instance1.getPrivate()); // 'I am private'

IP 주소 유효성 검사

주어진 문자열이 유효한 IPv4 또는 IPv6 주소인지 확인하는 함수를 작성합니다.

설명: IPv4의 경우, 점으로 구분된 4개의 숫자(0-255)를 확인하고 선행 0이 없어야 합니다('0' 자체는 제외). IPv6의 경우, 콜론으로 구분된 8개의 1-4 헥사(16진수) 숫자 그룹을 확인합니다('::'와 같은 변형 처리). 정규식을 사용할 수 있지만, 인터뷰에서는 수동 구문 분석이 더 명확할 때가 많습니다.

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

피크 요소 찾기

피크 요소는 이웃보다 엄격하게 큰 요소입니다. 입력 배열 nums가 주어졌을 때, 피크 요소를 찾아 해당 인덱스를 반환합니다.

설명: 어떤 피크라도 상관없고, nums[-1]과 nums[n]은 -Infinity로 간주되므로 수정된 이진 검색을 사용할 수 있습니다. nums[mid]가 nums[mid+1]보다 작으면 피크는 오른쪽에 존재해야 합니다. 그렇지 않으면 피크는 왼쪽에 존재해야 합니다(또는 mid 자체가 피크).

function findPeakElement(nums) { let left = 0; let right = nums.length - 1; while (left < right) { let mid = Math.floor((left + right) / 2); if (nums[mid] < nums[mid + 1]) { left = mid + 1; // Peak is to the right } else { right = mid; // Peak is mid or to the left } } return left; // 'left' will be the index of a peak } console.log(findPeakElement([1, 2, 3, 1])); // 2 (index of 3) console.log(findPeakElement([1, 2, 1, 3, 5, 6, 4])); // 5 (index of 6) or 1 (index of 2)

비트 계산

정수 n이 주어졌을 때, 길이가 n + 1인 배열 ans를 반환합니다. 여기서 각 i(0 <= i <= n)에 대해 ans[i]는 i의 이진 표현에서 1의 개수입니다.

설명: 동적 프로그래밍을 사용하여 이 문제를 해결할 수 있습니다. dp[i] = dp[i >> 1] + (i & 1) 관계에 주목하십시오. i의 1의 개수는 i를 오른쪽으로 시프트한(즉, i/2) 1의 개수에 i가 홀수이면 1을 더한 값입니다.

function countBits(n) { const dp = new Array(n + 1).fill(0); for (let i = 1; i <= n; i++) { dp[i] = dp[i >> 1] + (i & 1); // Or: dp[i] = dp[i & (i - 1)] + 1; (Removes last set bit) } return dp; } console.log(countBits(5)); // [0, 1, 1, 2, 1, 2]

2의 거듭제곱

정수 n이 주어졌을 때, 2의 거듭제곱이면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

설명: 2의 거듭제곱은 이진 표현에서 정확히 하나의 '1' 비트를 갖습니다(예: 1=1, 2=10, 4=100, 8=1000). 영리한 비트 연산 트릭은 n > 0이고 (n & (n - 1)) === 0인지 확인하는 것입니다. n이 2의 거듭제곱이면 n-1은 해당 '1' 아래의 모든 비트가 '1'로 설정됩니다. 이들을 AND 연산하면 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

간격 병합

intervals[i] = [starti, endi] 형식의 간격 배열이 주어졌을 때, 모든 겹치는 간격을 병합하고 겹치지 않는 간격 배열을 반환합니다.

설명: 먼저, 시작 시간을 기준으로 간격을 정렬합니다. 그런 다음, 정렬된 간격을 반복합니다. 현재 간격이 결과 목록의 마지막 간격과 겹치면 마지막 간격의 종료 시간을 업데이트하여 병합합니다. 그렇지 않으면 현재 간격을 결과 목록에 추가합니다.

function mergeIntervals(intervals) { if (intervals.length === 0) return []; intervals.sort((a, b) => a[0] - b[0]); const merged = [intervals[0]]; for (let i = 1; i < intervals.length; i++) { const last = merged[merged.length - 1]; const current = intervals[i]; if (current[0] <= last[1]) { // Overlap: merge last[1] = Math.max(last[1], current[1]); } else { // No overlap: add new interval merged.push(current); } } return merged; } console.log(mergeIntervals([[1, 3], [2, 6], [8, 10], [15, 18]])); // [[1, 6], [8, 10], [15, 18]]

단어 나누기

문자열 s와 문자열 딕셔너리 wordDict가 주어졌을 때, s가 하나 이상의 딕셔너리 단어로 공백으로 구분된 시퀀스로 분할될 수 있으면 true를 반환합니다.

설명: 동적 프로그래밍을 사용합니다. dp라는 부울 배열을 생성하고 dp[i]는 부분 문자열 s[0...i-1]이 분할될 수 있으면 true입니다. dp[i]dp[j]가 true이고 부분 문자열 s[j...i-1]이 딕셔너리에 있는 j < i가 존재하면 true입니다.

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

Array.prototype.flat 구현

지정된 깊이까지 모든 하위 배열 요소를 재귀적으로 연결하여 새 배열을 만드는 Array.prototype.flat의 자체 버전을 구현합니다.

설명: 재귀를 사용합니다. 배열을 반복합니다. 요소가 배열이고 현재 깊이가 지정된 깊이보다 작으면 재귀적으로 flatten을 호출합니다. 그렇지 않으면 요소를 푸시합니다. 기본 깊이 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]

문자열의 단어 뒤집기

입력 문자열 s가 주어졌을 때, 단어의 순서를 뒤집습니다.

설명: '단어'는 공백이 아닌 문자의 시퀀스로 정의됩니다. 문자열을 다듬고, 하나 이상의 공백으로 분할하고, 여러 공백으로 인해 발생하는 빈 문자열을 필터링하고, 배열을 뒤집고, 단일 공백으로 다시 결합합니다.

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'

쿼리 문자열 파서

URL 쿼리 문자열을 객체로 구문 분석하는 함수를 작성합니다.

설명: 문자열을 &로 분할합니다. 각 부분에 대해 =로 분할하여 키와 값을 가져옵니다. 키와 값 모두에 대해 URI 구성 요소를 디코딩합니다. 키가 여러 번 나타나거나 값이 없는 경우를 처리합니다(배열 생성).

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 }

유효성 검사를 위한 Proxy 사용

객체 속성 유효성 검사를 위해 Proxy 객체를 사용하는 방법을 보여줍니다.

설명: Proxy를 사용하면 객체에서 수행되는 작업을 가로채고 사용자 지정할 수 있습니다. set 트랩을 사용하여 속성에 값을 할당하기 전에 유효성을 검사합니다. 유효성 검사에 실패하면 오류를 발생시킵니다.

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

회의실

회의 시간 간격 배열 [[start1, end1], [start2, end2], ...]이 주어졌을 때, 한 사람이 모든 회의에 참석할 수 있는지 확인합니다.

설명: 한 사람이 모든 회의에 참석할 수 있다면, 두 회의가 겹치지 않는다는 의미입니다. 이를 확인하려면 시작 시간을 기준으로 간격을 정렬합니다. 그런 다음, 정렬된 간격을 반복하면서 현재 회의의 시작 시간이 이전 회의의 종료 시간보다 빠른지 확인합니다. 그렇다면 겹침이 감지되어 그 사람은 모든 회의에 참석할 수 없습니다.

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

Promise.any 구현

Promise.any와 유사하게 작동하는 함수를 구현합니다.

설명: Promise.any는 프로미스 배열을 취하고 단일 프로미스를 반환합니다. 이 프로미스는 입력 프로미스 중 하나라도 이행되는 즉시 이행됩니다. 모든 입력 프로미스가 거부되면 모든 거부 이유를 포함하는 AggregateError와 함께 거부됩니다.

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

옵저버 패턴

JavaScript에서 옵저버(Pub/Sub) 디자인 패턴을 구현합니다.

설명: 옵저버 패턴은 객체 간의 일대다 종속성을 정의합니다. 한 객체(주체)가 상태를 변경하면 모든 종속 객체(옵저버)는 자동으로 알림을 받고 업데이트됩니다. 주체는 옵저버 목록을 유지하고 추가, 제거 및 알림 메서드를 제공합니다.

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

중복 숫자 찾기

n + 1개의 정수(각 정수는 [1, n] 범위 내)를 포함하는 정수 배열 nums가 주어졌을 때, 반복되는 숫자를 찾습니다.

설명: 숫자가 [1, n] 범위에 있고 배열 크기가 n+1이므로 최소한 하나의 중복이 보장됩니다. 이는 '연결 리스트에서 사이클 찾기' 문제(플로이드의 토끼와 거북이)에 매핑될 수 있습니다. 배열 값을 포인터로 취급합니다: index -> nums[index].

function findDuplicate(nums) { let tortoise = nums[0]; let hare = nums[0]; // Phase 1: Find the intersection point do { tortoise = nums[tortoise]; hare = nums[nums[hare]]; } while (tortoise !== hare); // Phase 2: Find the entrance to the cycle let ptr1 = nums[0]; let ptr2 = tortoise; while (ptr1 !== ptr2) { ptr1 = nums[ptr1]; ptr2 = nums[ptr2]; } return ptr1; } console.log(findDuplicate([1, 3, 4, 2, 2])); // 2 console.log(findDuplicate([3, 1, 3, 4, 2])); // 3

기본 HTML 새니타이저

잠재적으로 유해한 태그(<script> 등)를 제거하고 안전한 태그(<p>, <b> 등)를 유지하는 기본 HTML 문자열 새니타이저 함수를 작성합니다.

설명: 이것은 복잡한 주제이며 실제 새니타이저는 정교합니다. 인터뷰의 경우, 기본 접근 방식은 특정 태그를 제거하거나 태그의 화이트리스트만 허용하기 위해 정규식을 사용하는 것을 포함할 수 있습니다. 이는 프로덕션에서 안전하지 않습니다.

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

편집 거리

두 문자열 word1과 word2가 주어졌을 때, word1을 word2로 변환하는 데 필요한 최소 작업(삽입, 삭제 또는 대체) 수를 반환합니다.

설명: 이것은 고전적인 동적 프로그래밍 문제(레벤슈타인 거리)입니다. dp라는 2D 배열을 생성하고 dp[i][j]는 word1의 처음 i개 문자와 word2의 처음 j개 문자 사이의 편집 거리입니다. 재귀 관계는 삽입, 삭제 및 대체 비용을 고려합니다.

function minDistance(word1, word2) { const m = word1.length, n = word2.length; const dp = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 0; i <= m; i++) dp[i][0] = i; for (let j = 0; j <= n; j++) dp[0][j] = j; for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { const cost = word1[i - 1] === word2[j - 1] ? 0 : 1; dp[i][j] = Math.min( dp[i - 1][j] + 1, // Deletion dp[i][j - 1] + 1, // Insertion dp[i - 1][j - 1] + cost // Substitution/Match ); } } return dp[m][n]; } console.log(minDistance('horse', 'ros')); // 3 console.log(minDistance('intention', 'execution')); // 5

가장 긴 증가하는 부분 수열 (LIS)

정수 배열 nums가 주어졌을 때, 가장 긴 엄격하게 증가하는 부분 수열의 길이를 반환합니다.

설명: 동적 프로그래밍을 사용합니다. dp[i]를 인덱스 i에서 끝나는 LIS의 길이라고 합니다. dp[i]를 계산하려면 nums[j] < nums[i]인 모든 j < i를 찾고 dp[i] = 1 + max(dp[j])를 취합니다. 최종 답변은 dp 배열의 최댓값입니다.

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

N-Queens 문제

N-Queens 퍼즐은 N×N 체스판에 N개의 체스 여왕을 서로 위협하지 않도록 배치하는 문제입니다. N이 주어졌을 때, 유효한 해결책 하나(또는 모두)를 반환합니다.

설명: 백트래킹을 사용합니다. 여왕을 행별로 배치합니다. 각 행에 대해 각 열에 여왕을 배치해 봅니다. 배치하기 전에 제안된 위치가 안전한지(이전 행의 여왕에게 공격받지 않는지) 확인합니다. 안전하면 배치하고 다음 행에 대해 재귀적으로 호출합니다. 재귀가 실패하면 백트래킹(여왕 제거)하고 다음 열을 시도합니다.

function solveNQueens(n) { const results = []; const board = Array(n).fill('.').map(() => Array(n).fill('.')); function isSafe(row, col) { for (let i = 0; i < row; i++) if (board[i][col] === 'Q') return false; // Check col for (let i = row - 1, j = col - 1; i >= 0 && j >= 0; i--, j--) if (board[i][j] === 'Q') return false; // Check diag up-left for (let i = row - 1, j = col + 1; i >= 0 && j < n; i--, j++) if (board[i][j] === 'Q') return false; // Check diag up-right return true; } function backtrack(row) { if (row === n) { results.push(board.map(r => r.join(''))); return; } for (let col = 0; col < n; col++) { if (isSafe(row, col)) { board[row][col] = 'Q'; backtrack(row + 1); board[row][col] = '.'; // Backtrack } } } backtrack(0); return results; } console.log(solveNQueens(4)); // [ [ '.Q..', '...Q', 'Q...', '..Q.' ], [ '..Q.', 'Q...', '...Q', '.Q..' ] ]

WeakMap을 사용하여 비공개 데이터 저장

WeakMap을 사용하여 클래스 인스턴스에 대한 비공개 데이터를 저장하는 방법을 보여줍니다.

설명: WeakMap을 사용하면 객체가 더 이상 참조되지 않을 때 가비지 컬렉션을 방지하지 않는 방식으로 객체와 데이터를 연결할 수 있습니다. 이는 네이티브 비공개 필드를 널리 사용할 수 있기 전의 JavaScript 클래스에서 '비공개' 멤버를 만들거나 특정 사용 사례에 유용합니다.

const privateData = new WeakMap(); class Person { constructor(name, age) { privateData.set(this, { name: name, age: age }); } getName() { return privateData.get(this).name; } getAge() { return privateData.get(this).age; } celebrateBirthday() { privateData.get(this).age++; } } const person = new Person('Alice', 30); console.log(person.getName()); // Alice person.celebrateBirthday(); console.log(person.getAge()); // 31 // console.log(person.name); // undefined - 'name' isn't a public property // console.log(privateData.get(person)); // Accessible, but not directly via 'person.'

Promise.allSettled 구현

Promise.allSettled와 유사하게 작동하는 함수를 구현합니다.

설명: Promise.allSettled는 프로미스 배열을 취하고 단일 프로미스를 반환합니다. 이 프로미스는 모든 입력 프로미스가 처리될 때(이행 또는 거부) 이행됩니다. 이행 값은 각 프로미스의 결과를 설명하는 객체 배열입니다({status: 'fulfilled', value: ...} 또는 {status: 'rejected', reason: ...}).

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

두 정렬된 배열의 중앙값 찾기

크기가 각각 m과 n인 두 개의 정렬된 배열 nums1과 nums2가 주어졌을 때, 두 정렬된 배열의 중앙값을 반환합니다.

설명: 이것은 더 작은 배열에 대한 이진 검색 접근 방식으로 자주 해결되는 어려운 문제입니다. 목표는 두 배열을 왼쪽의 모든 요소가 오른쪽의 모든 요소보다 작거나 같고, 왼쪽의 요소 수가 오른쪽의 요소 수와 같거나(또는 하나 더 많게) 분할하는 것입니다. 그러면 경계 요소에서 중앙값을 계산할 수 있습니다.

function findMedianSortedArrays(nums1, nums2) { if (nums1.length > nums2.length) [nums1, nums2] = [nums2, nums1]; // Ensure nums1 is smaller const m = nums1.length, n = nums2.length; let low = 0, high = m; while (low <= high) { const partitionX = Math.floor((low + high) / 2); const partitionY = Math.floor((m + n + 1) / 2) - partitionX; const maxX = (partitionX === 0) ? -Infinity : nums1[partitionX - 1]; const minX = (partitionX === m) ? Infinity : nums1[partitionX]; const maxY = (partitionY === 0) ? -Infinity : nums2[partitionY - 1]; const minY = (partitionY === n) ? Infinity : nums2[partitionY]; if (maxX <= minY && maxY <= minX) { if ((m + n) % 2 === 0) { return (Math.max(maxX, maxY) + Math.min(minX, minY)) / 2; } else { return Math.max(maxX, maxY); } } else if (maxX > minY) { high = partitionX - 1; } else { low = partitionX + 1; } } throw new Error('Input arrays are not sorted'); } console.log(findMedianSortedArrays([1, 3], [2])); // 2.0 console.log(findMedianSortedArrays([1, 2], [3, 4])); // 2.5

스도쿠 해결사

빈 셀을 채워서 스도쿠 퍼즐을 푸는 프로그램을 작성합니다.

설명: 백트래킹을 사용합니다. 빈 셀을 찾습니다. 1부터 9까지의 숫자로 채워봅니다. 각 숫자에 대해 유효한지(스도쿠 규칙을 위반하지 않는지) 확인합니다. 유효하면 재귀적으로 해결사를 호출합니다. 재귀가 true를 반환하면 해결책이 발견된 것입니다. 그렇지 않으면 백트래킹(셀 재설정)하고 다음 숫자를 시도합니다.

function solveSudoku(board) { function isValid(row, col, numStr) { for (let i = 0; i < 9; i++) { if (board[row][i] === numStr) return false; // Check row if (board[i][col] === numStr) return false; // Check col const boxRow = 3 * Math.floor(row / 3) + Math.floor(i / 3); const boxCol = 3 * Math.floor(col / 3) + i % 3; if (board[boxRow][boxCol] === numStr) return false; // Check box } return true; } function backtrack() { for (let r = 0; r < 9; r++) { for (let c = 0; c < 9; c++) { if (board[r][c] === '.') { for (let n = 1; n <= 9; n++) { const numStr = String(n); if (isValid(r, c, numStr)) { board[r][c] = numStr; if (backtrack()) return true; board[r][c] = '.'; // Backtrack } } return false; // No valid number found } } } return true; // Board solved } backtrack(); return board; } // Example requires a 9x9 board with '.' for empty cells.

기본 미들웨어 패턴 구현

웹 프레임워크에서 자주 볼 수 있는 간단한 미들웨어 패턴을 구현합니다.

설명: 미들웨어 함수는 최종 핸들러에 도달하기 전에 요청을 처리합니다. 각 미들웨어는 요청/응답을 수정하거나 다음 미들웨어에 제어를 전달할 수 있습니다. 미들웨어 함수 목록을 관리하고 순서대로 실행하는 클래스 또는 객체를 만듭니다.

class App { constructor() { this.middlewares = []; } use(fn) { this.middlewares.push(fn); } handle(request) { let index = 0; const next = () => { if (index < this.middlewares.length) { const middleware = this.middlewares[index++]; middleware(request, next); } }; next(); } } const app = new App(); app.use((req, next) => { console.log('1: Logging...'); req.logged = true; next(); }); app.use((req, next) => { console.log('2: Authenticating...'); req.authed = true; next(); }); app.use((req, next) => { console.log('3: Final Handler:', req); }); app.handle({ data: 'some request' }); // 1: Logging... // 2: Authenticating... // 3: Final Handler: { data: 'some request', logged: true, authed: true }

방향성 그래프에서 사이클 감지

방향성 그래프가 주어졌을 때, 사이클이 포함되어 있는지 확인하는 함수를 작성합니다.

설명: 깊이 우선 탐색(DFS)을 사용합니다. 두 개의 집합을 유지합니다: visiting(현재 재귀 스택에 있는 노드) 및 visited(완전히 탐색된 노드). DFS 중에 visiting 집합에 있는 노드를 만나면 백 에지를 찾은 것이므로 사이클이 있음을 의미합니다.

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

Object.freeze 동작 구현

Object.freeze에 대해 설명하고 (얕은) 버전을 구현합니다.

설명: Object.freeze는 새 속성 추가, 기존 속성 제거, 기존 속성 또는 열거 가능성, 구성 가능성, 쓰기 가능성 변경을 방지합니다. 객체를 (얕게) 변경 불가능하게 만듭니다. Object.preventExtensions 및 Object.defineProperty를 사용하여 기존 속성을 쓰기 불가능하고 구성 불가능하게 만들 수 있습니다.

function myFreeze(obj) { Object.preventExtensions(obj); // Prevent adding new properties Object.keys(obj).forEach(key => { Object.defineProperty(obj, key, { writable: false, configurable: false }); }); return obj; } const obj = { a: 1, b: 2 }; myFreeze(obj); // obj.c = 3; // Fails silently (or throws in strict mode) // delete obj.a; // Fails silently (or throws in strict mode) // obj.a = 10; // Fails silently (or throws in strict mode) console.log(obj); // { a: 1, b: 2 }

부드러운 애니메이션을 위해 requestAnimationFrame 사용

requestAnimationFrame(rAF)에 대해 설명하고 간단한 애니메이션 루프에 사용하는 방법을 보여줍니다.

설명: requestAnimationFrame(rAF)은 브라우저에 애니메이션을 수행하고 다음 다시 그리기 전에 지정된 함수를 호출하여 애니메이션을 업데이트하도록 요청합니다. 브라우저의 새로 고침 빈도와 동기화되고 탭이 보이지 않을 때 일시 중지되므로 애니메이션에 setTimeout 또는 setInterval을 사용하는 것보다 효율적이고 부드럽습니다.

// HTML: <div id='box' style='width: 50px; height: 50px; background: red; position: absolute; left: 0px;'></div> const box = document.getElementById('box'); let position = 0; let startTime = null; function animate(currentTime) { if (!startTime) startTime = currentTime; const elapsedTime = currentTime - startTime; // Move 100px in 2 seconds (50px/sec) position = (elapsedTime / 2000) * 100; if (position < 200) { // Keep animating until it reaches 200px box.style.left = position + 'px'; requestAnimationFrame(animate); } else { box.style.left = '200px'; } } // Start the animation // requestAnimationFrame(animate); // Uncomment to run in browser console.log('Use requestAnimationFrame for browser animations.');

Array.prototype.some 구현

Array.prototype.some의 자체 버전을 구현합니다.

설명: some은 배열의 하나 이상의 요소가 제공된 함수에 의해 구현된 테스트를 통과하는지 여부를 테스트합니다. 콜백이 true를 반환하는 요소를 찾으면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

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

Array.prototype.every 구현

Array.prototype.every의 자체 버전을 구현합니다.

설명: every는 배열의 모든 요소가 제공된 함수에 의해 구현된 테스트를 통과하는지 여부를 테스트합니다. 모든 요소가 통과하면(또는 배열이 비어 있으면) true를 반환하고, 요소 중 하나라도 실패하면 false를 반환합니다.

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 구현

Array.prototype.findIndex의 자체 버전을 구현합니다.

설명: findIndex는 제공된 테스트 함수를 만족하는 배열의 첫 번째 요소의 인덱스를 반환합니다. 그렇지 않으면 -1을 반환하며, 이는 테스트를 통과한 요소가 없음을 나타냅니다.

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

웹 워커란 무엇인가요?

웹 워커가 무엇이며 주요 사용 사례에 대해 설명합니다.

설명: 웹 워커는 웹 콘텐츠가 백그라운드 스레드에서 스크립트를 실행하는 수단입니다. 주요 사용 사례는 UI 업데이트 및 사용자 상호 작용을 처리하는 기본 스레드에서 오래 실행되거나 계산 집약적인 작업을 오프로드하여 UI가 응답하지 않거나 '멈추는' 것을 방지하는 것입니다. 워커는 postMessage 및 onmessage를 통해 기본 스레드와 통신합니다.

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

디코드 방법

A-Z 문자가 포함된 메시지는 'A' -> 1, 'B' -> 2, ..., 'Z' -> 26 매핑을 사용하여 숫자로 인코딩될 수 있습니다. 숫자만 포함하는 문자열 s가 주어졌을 때, 디코드할 수 있는 방법의 수를 반환합니다.

설명: 동적 프로그래밍을 사용합니다. dp[i]s[0...i-1]을 디코드하는 방법의 수라고 합니다. dp[i]dp[i-1](s[i-1]이 유효한 1자리 코드인 경우) 및 dp[i-2](s[i-2...i-1]이 유효한 2자리 코드인 경우)에 따라 달라집니다.

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

비트 단위 덧셈 (+ 없이)

  • 연산자를 사용하지 않고 두 정수를 더하는 함수를 작성합니다.

설명: 비트 연산자를 사용합니다. 합계는 a ^ b(캐리 없는 합계)로 계산할 수 있으며, 캐리는 (a & b) << 1로 계산할 수 있습니다. 캐리가 0이 될 때까지 이 과정을 반복합니다.

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

회문(숫자) 확인

정수 x가 주어졌을 때, x가 회문이면 true를 반환하고, 그렇지 않으면 false를 반환합니다.

설명: 음수는 회문이 아닙니다. 숫자를 문자열로 변환하고 문자열이 회문인지(앞뒤로 읽어도 동일한지) 확인합니다. 또는 숫자를 수학적으로 뒤집고 비교합니다.

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

팩토리 패턴

JavaScript에서 팩토리 디자인 패턴을 구현합니다.

설명: 팩토리 패턴은 상위 클래스에서 객체를 생성하기 위한 인터페이스를 제공하지만, 하위 클래스가 생성될 객체의 유형을 변경할 수 있도록 합니다. 정확한 클래스를 지정하지 않고 객체를 생성하는 데 유용합니다.

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

Symbol 설명 및 사용 사례

JavaScript에서 Symbol이 무엇인지 설명하고 '비공개' 속성 생성 또는 고유한 객체 키와 같은 사용 사례를 제공합니다.

설명: Symbol은 ES6에 도입된 원시 데이터 유형입니다. 해당 인스턴스는 고유하며 변경 불가능합니다. 다른 라이브러리 간의 이름 충돌을 피하거나 의사 비공개 속성을 생성하기 위해 객체 속성의 키로 자주 사용됩니다(실제로 비공개는 아니지만 일반적인 반복이나 Object.keys를 통해 접근할 수 없습니다).

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

arguments를 실제 배열로 변환

비화살표 함수에서 사용할 수 있는 arguments 객체를 실제 JavaScript 배열로 어떻게 변환할 수 있습니까?

설명: arguments 객체는 배열처럼 보이지만 배열이 아닙니다. map, filter와 같은 메서드가 없습니다. 다음을 사용하여 변환할 수 있습니다:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (ES6 스프레드 구문)
  3. Array.prototype.slice.call(arguments) (이전 방식)
function sumAll() { // 1. Array.from const args1 = Array.from(arguments); console.log('Using Array.from:', args1.reduce((a, b) => a + b, 0)); // 2. Spread Syntax const args2 = [...arguments]; console.log('Using Spread:', args2.reduce((a, b) => a + b, 0)); // 3. Slice const args3 = Array.prototype.slice.call(arguments); console.log('Using Slice:', args3.reduce((a, b) => a + b, 0)); } sumAll(1, 2, 3, 4); // Using Array.from: 10 // Using Spread: 10 // Using Slice: 10