Live Coding

ตรวจสอบว่าอ็อบเจกต์ว่างเปล่าหรือไม่

จะตรวจสอบได้อย่างไรว่าอ็อบเจกต์ JavaScript ว่างเปล่า?

คำอธิบาย: อ็อบเจกต์จะว่างเปล่าหากไม่มีคุณสมบัติที่แจกแจงได้ของตัวเอง เราสามารถใช้ 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'

ตรวจสอบ Palindrome

เขียนฟังก์ชันที่ตรวจสอบว่าสตริงที่กำหนดเป็น Palindrome หรือไม่

คำอธิบาย: Palindrome คือคำหรือวลีที่อ่านเหมือนกันทั้งไปข้างหน้าและข้างหลัง เราสามารถตรวจสอบได้โดยการกลับสตริง (โดยไม่สนใจตัวพิมพ์และอักขระที่ไม่ใช่ตัวอักษรและตัวเลขเพื่อการตรวจสอบที่แข็งแกร่งขึ้น แต่ในที่นี้เราจะตรวจสอบแบบง่ายๆ) แล้วนำไปเปรียบเทียบกับต้นฉบับ

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

FizzBuzz

เขียนโปรแกรมที่พิมพ์ตัวเลขตั้งแต่ 1 ถึง n แต่สำหรับพหุคูณของสาม ให้พิมพ์ 'Fizz' แทนตัวเลข และสำหรับพหุคูณของห้า ให้พิมพ์ 'Buzz' สำหรับตัวเลขที่เป็นพหุคูณของทั้งสามและห้า ให้พิมพ์ '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 แล้วแปลงกลับเป็นอาร์เรย์ได้

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

ตรวจสอบ Anagram

เขียนฟังก์ชันเพื่อตรวจสอบว่าสตริงสองสตริงเป็น Anagram ของกันและกันหรือไม่

คำอธิบาย: Anagram คือคำที่เกิดจากการจัดเรียงตัวอักษรของคำอื่นใหม่ หากต้องการตรวจสอบ เราสามารถล้าง จัดเรียง และเปรียบเทียบสตริงได้ การล้างเกี่ยวข้องกับการลบอักขระที่ไม่ใช่ตัวอักษรและตัวเลข และแปลงเป็นตัวพิมพ์เดียวกัน

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

คำนวณ Factorial

เขียนฟังก์ชันเพื่อคำนวณแฟกทอเรียลของจำนวนเต็มที่ไม่เป็นลบ

คำอธิบาย: แฟกทอเรียล (n!) ของตัวเลขคือผลคูณของจำนวนเต็มบวกทั้งหมดที่น้อยกว่าหรือเท่ากับ 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

ทำให้เป็นอาร์เรย์แบน

เขียนฟังก์ชันเพื่อทำให้อาร์เรย์ที่ซ้อนกันแบนลง สำหรับความง่าย ให้สมมติว่ามีการซ้อนเพียงหนึ่งระดับ

คำอธิบาย: สำหรับการซ้อนเพียงระดับเดียว คุณสามารถใช้ Array.prototype.concat() ร่วมกับตัวดำเนินการกระจาย หรือเมธอด flat() (ES2019)

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

เปลี่ยนประโยคเป็น Title Case

เขียนฟังก์ชันที่แปลงประโยคเป็น title case (อักษรตัวแรกของแต่ละคำเป็นตัวพิมพ์ใหญ่)

คำอธิบาย: แยกประโยคเป็นคำต่างๆ จากนั้นวนซ้ำแต่ละคำ ทำให้ตัวอักษรตัวแรกเป็นตัวพิมพ์ใหญ่และส่วนที่เหลือเป็นตัวพิมพ์เล็ก สุดท้าย รวมคำกลับเข้าด้วยกัน

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

คำอธิบาย: วิธีทั่วไปคือการใช้ hash map (หรืออ็อบเจกต์ JavaScript) เพื่อจัดเก็บตัวเลขและดัชนีขณะที่คุณวนซ้ำ สำหรับแต่ละตัวเลข ให้ตรวจสอบว่า target - current_number มีอยู่ใน map หรือไม่

function twoSum(nums, target) { const map = {}; for (let i = 0; i < nums.length; i++) { const complement = target - nums[i]; if (map[complement] !== undefined) { return [map[complement], i]; } map[nums[i]] = i; } return []; // หรือ null หรือส่งข้อผิดพลาดหากไม่มีคำตอบ } console.log(twoSum([2, 7, 11, 15], 9)); // [0, 1]

การใช้ Closures เพื่อสร้าง Counter

สร้างฟังก์ชันที่คืนค่าฟังก์ชัน counter แต่ละครั้งที่เรียกใช้ฟังก์ชันที่คืนค่ามา ควรเพิ่มค่าและคืนค่าจำนวนนับ

คำอธิบาย: นี่เป็นการสาธิต closures ฟังก์ชันภายในสามารถเข้าถึงตัวแปร 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

ค้นหาตัวอักษรที่ไม่ซ้ำกันตัวแรก

เขียนฟังก์ชันที่ค้นหาตัวอักษรที่ไม่ซ้ำกันตัวแรกในสตริง

คำอธิบาย: คุณสามารถใช้ hash map เพื่อนับความถี่ของอักขระ ก่อนอื่น ให้วนซ้ำผ่านสตริงเพื่อสร้าง map ความถี่ จากนั้น วนซ้ำผ่านสตริงอีกครั้งและคืนค่าอักขระตัวแรกที่มีจำนวนนับเป็น 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'

การแบ่งกลุ่มอาร์เรย์ (Array Chunking)

เขียนฟังก์ชันที่แบ่งอาร์เรย์ออกเป็นกลุ่มตามขนาดที่กำหนด

คำอธิบาย: วนซ้ำผ่านอาร์เรย์ โดยนำส่วนของขนาดที่กำหนดและผลักเข้าไปในอาร์เรย์ใหม่ จัดการกรณีที่ส่วนสุดท้ายอาจมีขนาดเล็กกว่า

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 จะเรียกใช้ฟังก์ชันเรียกกลับ 'reducer' ที่ผู้ใช้กำหนดกับแต่ละองค์ประกอบของอาร์เรย์ โดยส่งผ่านค่าที่คืนจากการคำนวณในองค์ประกอบก่อนหน้า ผลลัพธ์สุดท้ายของการเรียกใช้ reducer กับองค์ประกอบทั้งหมดของอาร์เรย์คือค่าเดียว จัดการอาร์กิวเมนต์ค่าเริ่มต้น

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

Memoization - ลำดับ Fibonacci

ใช้งานฟังก์ชัน Fibonacci โดยใช้ memoization เพื่อเพิ่มประสิทธิภาพ

คำอธิบาย: ลำดับ Fibonacci เกี่ยวข้องกับการคำนวณซ้ำๆ Memoization จะจัดเก็บผลลัพธ์ของการเรียกใช้ฟังก์ชันที่ซับซ้อนและคืนค่าผลลัพธ์ที่แคชไว้เมื่ออินพุตเดียวกันเกิดขึ้นอีกครั้ง

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 (เร็วกว่าแบบไม่ใช้ memoized มาก)

ตรวจสอบวงเล็บที่สมดุล

เขียนฟังก์ชันเพื่อตรวจสอบว่าสตริงที่มีวงเล็บ {}[]() สมดุลหรือไม่

คำอธิบาย: ใช้ stack เมื่อพบวงเล็บเปิด ให้ผลักเข้าไปใน stack เมื่อพบวงเล็บปิด ให้ตรวจสอบว่า stack ว่างเปล่าหรือไม่ หรือถ้าองค์ประกอบบนสุดเป็นวงเล็บเปิดที่ตรงกัน หากตรงกัน ให้ pop stack หากไม่ หรือถ้า stack ว่างเปล่า แสดงว่าไม่สมดุล ในท้ายที่สุด stack ที่ว่างเปล่าหมายถึงสมดุล

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

การใช้งาน Simple Queue

ใช้งานโครงสร้างข้อมูล Queue ด้วยเมธอด enqueue (เพิ่มไปด้านหลัง) และ dequeue (ลบออกจากด้านหน้า)

คำอธิบาย: Queue เป็นไปตามหลักการ First-In, First-Out (FIFO) สามารถใช้อาร์เรย์ได้ โดยใช้ push สำหรับ enqueue และ shift สำหรับ dequeue

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

การใช้งาน Simple Stack

ใช้งานโครงสร้างข้อมูล Stack ด้วยเมธอด push (เพิ่มไปด้านบน) และ pop (ลบออกจากด้านบน)

คำอธิบาย: Stack เป็นไปตามหลักการ Last-In, First-Out (LIFO) สามารถใช้อาร์เรย์ได้ โดยใช้เมธอด push และ pop

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

ค้นหาตัวเลขที่หายไปในลำดับ

กำหนดอาร์เรย์ที่มี n ตัวเลขที่ไม่ซ้ำกันตั้งแต่ 0, 1, 2, ..., 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

ฟังก์ชัน Debounce

ใช้งานฟังก์ชัน debounce Debouncing ช่วยให้มั่นใจได้ว่าฟังก์ชันจะไม่ถูกเรียกอีกครั้งจนกว่าจะผ่านไปช่วงเวลาหนึ่งโดยไม่มีการเรียกใช้

คำอธิบาย: ใช้ setTimeout และ clearTimeout แต่ละครั้งที่เรียกใช้ฟังก์ชันที่ debounced ให้ล้าง timeout ก่อนหน้าและตั้งค่าใหม่ การเรียกใช้ฟังก์ชันจริงจะเกิดขึ้นเมื่อ timeout เสร็จสมบูรณ์เท่านั้น

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 วินาทีหลังจากเรียกนี้

ฟังก์ชัน Throttle

ใช้งานฟังก์ชัน throttle Throttling ช่วยให้มั่นใจได้ว่าฟังก์ชันจะถูกเรียกใช้มากที่สุดหนึ่งครั้งในช่วงเวลาที่กำหนด

คำอธิบาย: ใช้แฟล็กเพื่อระบุว่าอนุญาตให้เรียกใช้หรือไม่ เมื่อถูกเรียก หากอนุญาต ให้ดำเนินการฟังก์ชัน ตั้งค่าแฟล็กเป็น 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); // จะบันทึกมากที่สุดหนึ่งครั้งต่อวินาที

ฟังก์ชัน Currying

เขียนฟังก์ชันที่รับฟังก์ชันที่มีสองอาร์กิวเมนต์และคืนค่าเวอร์ชัน curried ของมัน

คำอธิบาย: Currying แปลงฟังก์ชันที่มีหลายอาร์กิวเมนต์เป็นลำดับของฟังก์ชัน โดยแต่ละฟังก์ชันจะรับอาร์กิวเมนต์เดียว 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 อ็อบเจกต์

เขียนฟังก์ชันเพื่อดำเนินการ deep clone ของอ็อบเจกต์ JavaScript

คำอธิบาย: การโคลนแบบตื้น (shallow clone) จะคัดลอกเฉพาะคุณสมบัติระดับบนสุด การโคลนแบบลึก (deep clone) จะคัดลอกคุณสมบัติทั้งหมดแบบเรียกซ้ำ รวมถึงอ็อบเจกต์ที่ซ้อนกันและอาร์เรย์ วิธีง่ายๆ (มีข้อจำกัด เช่น ไม่จัดการฟังก์ชัน วันที่ regex ได้ดี) คือการใช้ JSON.parse(JSON.stringify(obj)) วิธีการแบบเรียกซ้ำจะแข็งแกร่งกว่า

function deepClone(obj) { // เวอร์ชันง่ายๆ (มีข้อจำกัด) try { return JSON.parse(JSON.stringify(obj)); } catch (e) { console.error("Cloning failed:", 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 (พิสูจน์ว่าเป็น deep clone) console.log(cloned.b.c); // 3

รับ Object Keys

จะรับอาร์เรย์ของ keys จากอ็อบเจกต์ได้อย่างไร?

คำอธิบาย: ใช้ Object.keys(obj)

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

รับ Object Values

จะรับอาร์เรย์ของ values จากอ็อบเจกต์ได้อย่างไร?

คำอธิบาย: ใช้ 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' ใน Global Scope

this หมายถึงอะไรใน global scope ในเบราว์เซอร์?

คำอธิบาย: ในบริบทการดำเนินการทั่วโลก (ภายนอกฟังก์ชันใดๆ) this หมายถึงอ็อบเจกต์ทั่วโลก ซึ่งคือ window ในเว็บเบราว์เซอร์

console.log(this === window); // true (ในสภาพแวดล้อมเบราว์เซอร์)

อธิบาย 'this' ใน Object Method

this หมายถึงอะไรเมื่อใช้ภายใน object method?

คำอธิบาย: เมื่อฟังก์ชันถูกเรียกใช้เป็นเมธอดของอ็อบเจกต์ this จะหมายถึงอ็อบเจกต์ที่เมธอดนั้นถูกเรียกใช้

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

อธิบาย 'this' กับ Arrow Functions

Arrow functions จัดการ this อย่างไร?

คำอธิบาย: Arrow functions ไม่มีบริบท this ของตนเอง แต่จะสืบทอด this จากขอบเขต (lexical) ที่อยู่รอบๆ ที่ถูกกำหนด

function MyClass() { this.value = 42; setTimeout(() => { console.log(this.value); // แสดง 42 เพราะ 'this' ถูกสืบทอดมา }, 100); } new MyClass();

ความแตกต่างระหว่าง `let`, `const`, และ `var`

ความแตกต่างหลักระหว่าง let, const, และ var คืออะไร?

คำอธิบาย: var เป็น function-scoped และ hoisted let และ const เป็น block-scoped และ hoisted แต่จะอยู่ใน 'temporal dead zone' จนกว่าจะมีการประกาศ 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 ใน JavaScript คืออะไร

คำอธิบาย: Promise คืออ็อบเจกต์ที่แสดงถึงการสำเร็จ (หรือล้มเหลว) ของการดำเนินการแบบอะซิงโครนัสและค่าที่เป็นผลลัพธ์ สามารถอยู่ในสถานะใดสถานะหนึ่งจากสามสถานะ: pending, fulfilled, หรือ rejected

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

การใช้ `async/await`

async และ await ช่วยให้การทำงานกับ Promises ง่ายขึ้นได้อย่างไร?

คำอธิบาย: async/await เป็น syntactic sugar สำหรับ Promises ทำให้โค้ดแบบอะซิงโครนัสดูและทำงานคล้ายกับโค้ดแบบซิงโครนัสมากขึ้น ฟังก์ชัน async จะคืนค่า Promise เสมอ และ await จะหยุดการทำงานจนกว่า Promise จะเสร็จสมบูรณ์

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

แปลง String เป็น Number

จะแปลงสตริงเป็นตัวเลขได้อย่างไร?

คำอธิบาย: ใช้ parseInt(), parseFloat(), หรือตัวดำเนินการบวกแบบ unary (+)

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

แปลง Number เป็น String

จะแปลงตัวเลขเป็นสตริงได้อย่างไร?

คำอธิบาย: ใช้ String(), number.toString(), หรือการเชื่อมสตริง

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

`JSON.stringify` คืออะไร?

JSON.stringify ทำอะไร?

คำอธิบาย: จะแปลงอ็อบเจกต์หรือค่า JavaScript เป็นสตริง JSON

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

`JSON.parse` คืออะไร?

JSON.parse ทำอะไร?

คำอธิบาย: จะแยกวิเคราะห์สตริง JSON โดยสร้างค่าหรืออ็อบเจกต์ JavaScript ที่อธิบายโดยสตริง

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

Spread Operator ใน Arrays

มีการใช้ spread operator กับ arrays อย่างไร?

คำอธิบาย: ช่วยให้ iterable (เช่น อาร์เรย์) สามารถขยายได้ในที่ที่คาดว่าจะมีศูนย์หรือมากกว่าหนึ่งอาร์กิวเมนต์หรือองค์ประกอบ มีประโยชน์สำหรับการคัดลอก การรวม และการเพิ่มองค์ประกอบ

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

Spread Operator ใน Objects

มีการใช้ spread operator กับ objects อย่างไร?

คำอธิบาย: ช่วยให้สามารถคัดลอกและรวมคุณสมบัติของอ็อบเจกต์ได้

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

Destructuring Arrays

อธิบาย array destructuring พร้อมตัวอย่าง

คำอธิบาย: เป็นไวยากรณ์ที่ทำให้สามารถแกะค่าออกจากอาร์เรย์ให้เป็นตัวแปรที่แตกต่างกันได้

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

Destructuring Objects

อธิบาย object destructuring พร้อมตัวอย่าง

คำอธิบาย: ทำให้สามารถแกะคุณสมบัติออกจากอ็อบเจกต์ให้เป็นตัวแปรที่แตกต่างกันได้

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 เวอร์ชันพื้นฐานได้อย่างไร?

คำอธิบาย: apply คล้ายกับ call แต่รับอาร์กิวเมนต์เป็นอาร์เรย์

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.

Explain Event Loop

คำอธิบาย: JavaScript เป็นแบบเธรดเดียว แต่ทำงานพร้อมกันได้โดยใช้ Event Loop Call Stack จัดการโค้ดแบบ synchronous ส่วน Web API จัดการการทำงานแบบ asynchronous (เช่น setTimeout, fetch) เมื่อการทำงานแบบ asynchronous เสร็จสิ้น, callback ของมันจะไปที่ Callback Queue (หรือ Microtask Queue สำหรับ Promises) Event Loop จะตรวจสอบตลอดเวลาว่า Call Stack ว่างหรือไม่ ถ้าว่าง, มันจะย้าย callback ถัดไปจาก queue ไปยัง stack เพื่อประมวลผล

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

Binary Search

ใช้งานฟังก์ชัน binary search สำหรับอาร์เรย์ที่จัดเรียงแล้ว

คำอธิบาย: Binary search ค้นหารายการในอาร์เรย์ที่จัดเรียงแล้วได้อย่างมีประสิทธิภาพ โดยการแบ่งช่วงการค้นหาออกเป็นครึ่งซ้ำๆ หากค่าของคีย์การค้นหาน้อยกว่ารายการตรงกลางของช่วง, ให้จำกัดช่วงไปที่ครึ่งล่าง หากไม่เป็นเช่นนั้น, ให้จำกัดช่วงไปที่ครึ่งบน คุณจะทำเช่นนี้จนกว่าจะพบค่าหรือช่วงว่างเปล่า

function binarySearch(sortedArray, key) { let start = 0; let end = sortedArray.length - 1; while (start <= end) { let middle = Math.floor((start + end) / 2); if (sortedArray[middle] === key) { return middle; // Found } else if (sortedArray[middle] < key) { start = middle + 1; // Search right half } else { end = middle - 1; // Search left half } } return -1; // Not found } console.log(binarySearch([1, 3, 5, 7, 9, 11], 7)); // 3 console.log(binarySearch([1, 3, 5, 7, 9, 11], 2)); // -1

Merge Sort

ใช้งานอัลกอริทึม Merge Sort

คำอธิบาย: Merge Sort เป็นอัลกอริทึมแบบแบ่งแล้วเอาชนะ มันแบ่งอาร์เรย์ที่ป้อนเข้าออกเป็นสองส่วนครึ่ง, เรียกใช้ตัวเองสำหรับสองส่วนครึ่ง, และจากนั้นรวมสองส่วนครึ่งที่จัดเรียงแล้วเข้าด้วยกัน ฟังก์ชัน merge เป็นสิ่งสำคัญ มันรวมอาร์เรย์ย่อยสองอาร์เรย์ที่จัดเรียงแล้วเข้าเป็นอาร์เรย์เดียวที่จัดเรียงแล้ว

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

Quick Sort

ใช้งานอัลกอริทึม Quick Sort

คำอธิบาย: Quick Sort เป็นอัลกอริทึมแบบแบ่งแล้วเอาชนะเช่นกัน มันเลือกองค์ประกอบหนึ่งเป็น pivot และแบ่งอาร์เรย์ที่กำหนดออกเป็นส่วนๆ รอบ pivot ที่เลือก องค์ประกอบที่เล็กกว่า pivot ไปทางซ้าย และองค์ประกอบที่ใหญ่กว่าไปทางขวา จากนั้นจะจัดเรียงอาร์เรย์ย่อยซ้ำๆ

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

Bubble Sort

ใช้งานอัลกอริทึม Bubble Sort

คำอธิบาย: Bubble Sort เป็นอัลกอริทึมการจัดเรียงที่เรียบง่าย ซึ่งจะวนซ้ำในรายการ, เปรียบเทียบองค์ประกอบที่อยู่ติดกัน, และสลับพวกมันหากอยู่ในลำดับที่ไม่ถูกต้อง การวนผ่านรายการจะทำซ้ำจนกว่ารายการจะถูกจัดเรียง

function bubbleSort(arr) { let n = arr.length; let swapped; do { swapped = false; for (let i = 0; i < n - 1; i++) { if (arr[i] > arr[i + 1]) { [arr[i], arr[i + 1]] = [arr[i + 1], arr[i]]; // Swap swapped = true; } } n--; // Optimization: last element is already in place } while (swapped); return arr; } console.log(bubbleSort([64, 34, 25, 12, 22, 11, 90])); // [11, 12, 22, 25, 34, 64, 90]

Longest Substring Without Repeating Characters

กำหนดสตริง, ค้นหาความยาวของสตริงย่อยที่ยาวที่สุดที่ไม่มีอักขระซ้ำ

คำอธิบาย: ใช้เทคนิค 'sliding window' รักษา window (substring) และชุดของอักขระใน window นั้น ขยาย window โดยการย้ายตัวชี้ขวา หากพบอักขระซ้ำ, ให้หด window จากซ้ายจนกว่าอักขระซ้ำจะถูกลบออก

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

Implement a Linked List

ใช้งาน Singly Linked List พร้อมเมธอด add และ print

คำอธิบาย: Linked list เป็นโครงสร้างข้อมูลเชิงเส้นที่องค์ประกอบไม่ได้จัดเก็บในตำแหน่งหน่วยความจำที่อยู่ติดกัน แต่ละองค์ประกอบ (node) จะชี้ไปยังถัดไป คุณต้องมีคลาส Node และคลาส LinkedList เพื่อจัดการ head และการเพิ่ม node ใหม่

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

Implement a Binary Search Tree (BST)

ใช้งาน Binary Search Tree พร้อมเมธอด insert และ find

คำอธิบาย: BST เป็นโครงสร้างข้อมูลแบบต้นไม้ไบนารีที่อิงตามโหนด ซึ่งมีคุณสมบัติดังต่อไปนี้: subtree ซ้ายของโหนดจะมีเฉพาะโหนดที่มีคีย์น้อยกว่าคีย์ของโหนด subtree ขวาของโหนดจะมีเฉพาะโหนดที่มีคีย์มากกว่าคีย์ของโหนด ทั้งสอง subtree ต้องเป็น binary search trees ด้วย

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

Rotate an Array

เขียนฟังก์ชันเพื่อหมุนอาร์เรย์ไปทางขวา k ขั้นตอน

คำอธิบาย: การหมุนอาร์เรย์หมายถึงการย้ายองค์ประกอบของมัน สำหรับการหมุนขวา, องค์ประกอบสุดท้ายจะกลายเป็นองค์ประกอบแรก การทำซ้ำเช่นนี้ k ครั้งจะใช้ได้แต่ไม่มีประสิทธิภาพ วิธีที่ดีกว่าคือการใช้การจัดการอาร์เรย์หรือคำนวณตำแหน่งใหม่

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

Find Intersection of Two Arrays

กำหนดอาร์เรย์สองตัว, เขียนฟังก์ชันเพื่อคำนวณส่วนที่ตัดกัน (องค์ประกอบที่เหมือนกันทั้งสอง) ของพวกมัน

คำอธิบาย: วิธีที่ดีคือการแปลงอาร์เรย์หนึ่งเป็น 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]

Group Anagrams

กำหนดอาร์เรย์ของสตริง, จัดกลุ่ม anagrams เข้าด้วยกัน

คำอธิบาย: สิ่งสำคัญคือการค้นหาลายเซ็นที่ไม่ซ้ำกันสำหรับแต่ละกลุ่มของ anagrams วิธีทั่วไปคือการจัดเรียงแต่ละคำตามตัวอักษร คำที่ได้สตริงที่จัดเรียงแล้วเหมือนกันคือ anagrams ใช้ hash map ที่มีคีย์เป็นคำที่จัดเรียงแล้วและค่าเป็นอาร์เรย์ของคำเดิม

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

Move Zeroes to End

กำหนดอาร์เรย์ nums, เขียนฟังก์ชันเพื่อย้าย 0 ทั้งหมดไปที่ท้ายสุดของมัน โดยยังคงรักษาลำดับสัมพัทธ์ขององค์ประกอบที่ไม่ใช่ศูนย์

คำอธิบาย: ใช้แนวทาง 'two-pointer' หรือ 'snowball' ตัวชี้หนึ่งจะติดตามตำแหน่งที่องค์ประกอบที่ไม่ใช่ศูนย์ถัดไปควรไป วนซ้ำผ่านอาร์เรย์ หากองค์ประกอบไม่ใช่ศูนย์, ให้วางไว้ที่ตำแหน่งของตัวชี้และเพิ่มตัวชี้ สุดท้าย, เติมส่วนที่เหลือด้วยศูนย์

function moveZeroes(nums) { let nonZeroIndex = 0; // Move all non-zero elements to the front for (let i = 0; i < nums.length; i++) { if (nums[i] !== 0) { nums[nonZeroIndex] = nums[i]; nonZeroIndex++; } } // Fill the rest with zeros for (let i = nonZeroIndex; i < nums.length; i++) { nums[i] = 0; } return nums; } console.log(moveZeroes([0, 1, 0, 3, 12])); // [1, 3, 12, 0, 0]

Maximum Subarray (Kadane's Algorithm)

กำหนดอาร์เรย์จำนวนเต็ม nums, ค้นหา subarray ที่ต่อเนื่องกัน (ที่มีอย่างน้อยหนึ่งหมายเลข) ที่มีผลรวมมากที่สุดและส่งคืนผลรวมนั้น

คำอธิบาย: อัลกอริทึมของ 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])

Permutations of a String

เขียนฟังก์ชันเพื่อสร้างการเรียงสับเปลี่ยนทั้งหมดของสตริงที่กำหนด

คำอธิบาย: โดยทั่วไปแล้วจะแก้ไขโดยใช้ recursion และ backtracking สำหรับแต่ละอักขระ, กำหนดและสร้างการเรียงสับเปลี่ยนซ้ำๆ สำหรับส่วนที่เหลือของสตริง กรณีพื้นฐาน: เมื่อสตริงมีอักขระเดียว, ให้ส่งคืนอักขระนั้น

function stringPermutations(str) { if (str.length === 0) return ['']; if (str.length === 1) return [str]; const results = []; for (let i = 0; i < str.length; i++) { const char = str[i]; const remainingChars = str.slice(0, i) + str.slice(i + 1); const perms = stringPermutations(remainingChars); for (const perm of perms) { results.push(char + perm); } } return [...new Set(results)]; // Use Set to handle duplicate chars if needed } console.log(stringPermutations('abc')); // ['abc', 'acb', 'bac', 'bca', 'cab', 'cba'] console.log(stringPermutations('aab')); // ['aab', 'aba', 'baa']

Greatest Common Divisor (GCD)

เขียนฟังก์ชันเพื่อค้นหา Greatest Common Divisor (GCD) ของสองจำนวน

คำอธิบาย: อัลกอริทึม Euclidean เป็นวิธีที่มีประสิทธิภาพ หาก b เป็น 0, a คือ GCD มิฉะนั้น, GCD ของ a และ b จะเหมือนกับ GCD ของ b และ a % b (เศษของการหาร a ด้วย b)

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

Least Common Multiple (LCM)

เขียนฟังก์ชันเพื่อค้นหา Least Common Multiple (LCM) ของสองจำนวน

คำอธิบาย: LCM สามารถคำนวณได้โดยใช้ GCD ด้วยสูตร: LCM(a, b) = |a * b| / GCD(a, b)

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

Implement Promise.all

ใช้งานฟังก์ชันที่ทำงานเหมือน Promise.all

คำอธิบาย: Promise.all รับอาร์เรย์ของ Promises และส่งคืน Promise เดียวที่ Resolve เมื่อ Promises อินพุตทั้งหมด Resolve, หรือ Reject หาก Promise อินพุตใดๆ Reject ค่าที่ Resolve คืออาร์เรย์ของค่าที่ Resolve

function myPromiseAll(promises) { return new Promise((resolve, reject) => { const results = []; let completedCount = 0; const numPromises = promises.length; if (numPromises === 0) { resolve([]); return; } promises.forEach((promise, index) => { Promise.resolve(promise) .then(value => { results[index] = value; completedCount++; if (completedCount === numPromises) { resolve(results); } }) .catch(reject); // Reject immediately on any error }); }); } // Example usage: const p1 = Promise.resolve(3); const p2 = 42; const p3 = new Promise((resolve) => setTimeout(resolve, 100, 'foo')); myPromiseAll([p1, p2, p3]).then(values => console.log(values)); // [3, 42, 'foo']

Invert Binary Tree

เขียนฟังก์ชันเพื่อ invert binary tree

คำอธิบาย: ในการ invert binary tree, คุณจะสลับลูกซ้ายและขวาสำหรับทุกโหนด สิ่งนี้สามารถทำได้แบบเรียกซ้ำหรือแบบวนซ้ำ (โดยใช้ queue หรือ stack)

class Node { constructor(val, left = null, right = null) { this.val = val; this.left = left; this.right = right; } } function invertTree(root) { if (root === null) { return null; } // Swap the children [root.left, root.right] = [root.right, root.left]; // Recur for left and right children invertTree(root.left); invertTree(root.right); return root; } // Example: 4 -> [2, 7] -> [1, 3, 6, 9] // Becomes: 4 -> [7, 2] -> [9, 6, 3, 1]

Max Depth of Binary Tree

กำหนด binary tree, ค้นหาความลึกสูงสุดของมัน

คำอธิบาย: ความลึกสูงสุดคือจำนวนโหนดตามเส้นทางที่ยาวที่สุดจากโหนด root ลงไปยังโหนดใบที่ไกลที่สุด สิ่งนี้สามารถพบได้แบบเรียกซ้ำ: MaxDepth = 1 + max(MaxDepth(ซ้าย), MaxDepth(ขวา)) กรณีพื้นฐานคือเมื่อโหนดเป็น 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; } // Example: 3 -> [9, 20] -> [null, null, 15, 7] const tree = new Node(3, new Node(9), new Node(20, new Node(15), new Node(7))); console.log(maxDepth(tree)); // 3

Best Time to Buy/Sell Stock

คุณได้รับอาร์เรย์ 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 (Buy at 1, Sell at 6) console.log(maxProfit([7, 6, 4, 3, 1])); // 0 (No profit possible)

Single Number

กำหนดอาร์เรย์จำนวนเต็มที่ไม่ว่างเปล่า nums, ทุกองค์ประกอบปรากฏสองครั้งยกเว้นหนึ่งตัว ค้นหาตัวเดียวเท่านั้น

คำอธิบาย: วิธีที่มีประสิทธิภาพมากในการแก้ปัญหานี้คือการใช้ตัวดำเนินการบิตwise XOR (^) การ XOR ตัวเลขด้วยตัวเองส่งผลให้เป็น 0 การ XOR ตัวเลขด้วย 0 ส่งผลให้เป็นตัวเลขนั้นเอง หากคุณ 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

Majority Element

กำหนดอาร์เรย์ nums ขนาด n, ส่งคืนองค์ประกอบส่วนใหญ่ องค์ประกอบส่วนใหญ่คือองค์ประกอบที่ปรากฏมากกว่า ⌊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

Climbing Stairs

คุณกำลังปีนบันได มันใช้ n ขั้นตอนเพื่อไปถึงด้านบน แต่ละครั้งคุณสามารถปีนได้ 1 หรือ 2 ขั้นตอน คุณสามารถปีนขึ้นไปด้านบนได้กี่วิธีที่แตกต่างกัน

คำอธิบาย: นี่คือปัญหา dynamic programming คลาสสิก คล้ายกับลำดับ Fibonacci จำนวนวิธีในการไปถึงขั้นที่ 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

Product of Array Except Self

กำหนดอาร์เรย์จำนวนเต็ม nums, ส่งคืนอาร์เรย์ answer ที่ answer[i] เท่ากับผลคูณขององค์ประกอบทั้งหมดของ nums ยกเว้น nums[i] ห้ามใช้ตัวดำเนินการหาร

คำอธิบาย: คุณสามารถแก้ไขปัญหานี้ได้โดยการคำนวณผลคูณคำนำหน้าและผลคูณคำต่อท้าย ผลลัพธ์ที่ดัชนี i คือผลคูณขององค์ประกอบทั้งหมดก่อนหน้า i คูณด้วยผลคูณขององค์ประกอบทั้งหมดหลังจาก i

function productExceptSelf(nums) { const n = nums.length; const answer = new Array(n).fill(1); // Calculate prefix products let prefix = 1; for (let i = 0; i < n; i++) { answer[i] = prefix; prefix *= nums[i]; } // Calculate suffix products and multiply with prefix let suffix = 1; for (let i = n - 1; i >= 0; i--) { answer[i] *= suffix; suffix *= nums[i]; } return answer; } console.log(productExceptSelf([1, 2, 3, 4])); // [24, 12, 8, 6]

Number of Islands

กำหนดแผนที่กริด 2D ของ '1' (แผ่นดิน) และ '0' (น้ำ), นับจำนวนเกาะ เกาะจะถูกล้อมรอบด้วยน้ำและเกิดจากการเชื่อมต่อแผ่นดินที่อยู่ติดกันในแนวนอนหรือแนวตั้ง

คำอธิบาย: วนซ้ำผ่านแต่ละเซลล์ของกริด หากคุณพบ '1' (แผ่นดิน), ให้เพิ่มจำนวนเกาะและเริ่มต้น Depth First Search (DFS) หรือ Breadth First Search (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'; // Mark as visited dfs(r + 1, c); dfs(r - 1, c); dfs(r, c + 1); dfs(r, c - 1); } for (let r = 0; r < rows; r++) { for (let c = 0; c < cols; c++) { if (grid[r][c] === '1') { count++; dfs(r, c); } } } return count; } const grid1 = [ ['1', '1', '1', '1', '0'], ['1', '1', '0', '1', '0'], ['1', '1', '0', '0', '0'], ['0', '0', '0', '0', '0'] ]; // console.log(numIslands(grid1)); // 1 (Need to run on a copy or restore grid)

LRU Cache

ออกแบบและใช้งานโครงสร้างข้อมูลสำหรับ Least Recently Used (LRU) Cache ควรสนับสนุนการทำงาน get และ put

คำอธิบาย: แคช LRU จะลบรายการที่ใช้งานน้อยที่สุดเมื่อถึงความจุ การใช้งานทั่วไปคือการใช้ Map (หรือ hash map) สำหรับการค้นหา O(1) โดยเฉลี่ย และ Doubly Linked List สำหรับการอัปเดตสถานะ 'ที่ใช้งานล่าสุด' แบบ O(1) JavaScript's 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); // Remove to re-insert at the 'end' (most recent) this.cache.set(key, value); return value; } put(key, value) { if (this.cache.has(key)) { this.cache.delete(key); } else if (this.cache.size >= this.capacity) { const oldestKey = this.cache.keys().next().value; this.cache.delete(oldestKey); } this.cache.set(key, value); } } const cache = new LRUCache(2); cache.put(1, 1); cache.put(2, 2); console.log(cache.get(1)); // 1 cache.put(3, 3); // Evicts 2 console.log(cache.get(2)); // -1

Generate Parentheses

กำหนด n คู่ของวงเล็บ, เขียนฟังก์ชันเพื่อสร้างการรวมกันทั้งหมดของวงเล็บที่จัดเรียงอย่างดี

คำอธิบาย: นี่เป็นปัญหา backtracking คลาสสิก รักษาจำนวนวงเล็บเปิดและปิด คุณสามารถเพิ่มวงเล็บเปิดได้หาก 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)); // ['((()))', '(()())', '(())()', '()(())', '()()()']

Container With Most Water

กำหนดจำนวนเต็มที่ไม่เป็นลบ n จำนวน a1, a2, ..., an, โดยแต่ละค่าแสดงถึงจุดที่พิกัด (i, ai) มีเส้นแนวตั้ง n เส้นถูกวาดเพื่อให้จุดสิ้นสุดทั้งสองของเส้น i อยู่ที่ (i, ai) และ (i, 0) ค้นหาเส้นสองเส้นซึ่งเมื่อรวมกับแกน x จะสร้างภาชนะที่บรรจุน้ำได้มากที่สุด

คำอธิบาย: ใช้แนวทาง two-pointer เริ่มต้นด้วยตัวชี้หนึ่งที่จุดเริ่มต้นและอีกตัวที่จุดสิ้นสุด คำนวณพื้นที่ พื้นที่ถูกจำกัดด้วยเส้นที่สั้นกว่า เพื่อค้นหาพื้นที่ที่ใหญ่ขึ้น, ย้ายตัวชี้ที่ชี้ไปยังเส้นที่สั้นกว่าเข้าด้านใน

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

กำหนดอาร์เรย์ nums ของจำนวนเต็ม n, มีองค์ประกอบ a, b, c ใน nums ที่ a + b + c = 0 หรือไม่ ค้นหา triplets ที่ไม่ซ้ำกันทั้งหมดในอาร์เรย์ที่ให้ผลรวมเป็นศูนย์

คำอธิบาย: ขั้นแรก, จัดเรียงอาร์เรย์ จากนั้น, วนซ้ำผ่านอาร์เรย์ด้วยตัวชี้หนึ่ง i สำหรับแต่ละ i, ใช้ตัวชี้อีกสองตัว, left (เริ่มต้นที่ i+1) และ right (เริ่มต้นที่ท้ายสุด), เพื่อค้นหาสองตัวเลขที่รวมกันได้เท่ากับ -nums[i] จัดการรายการที่ซ้ำกันเพื่อให้แน่ใจว่า triplets ไม่ซ้ำกัน

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

Search Insert Position

กำหนดอาร์เรย์จำนวนเต็มที่จัดเรียงแล้วที่ไม่ซ้ำกันและค่าเป้าหมาย, ส่งคืนดัชนีหากพบเป้าหมาย หากไม่พบ, ส่งคืนดัชนีที่ควรจะถูกแทรกตามลำดับ

คำอธิบาย: นี่คือการเปลี่ยนแปลงของ binary search ทำ binary search, แต่ถ้าไม่พบองค์ประกอบ, ตัวชี้ 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; // If not found, start is the insertion point } console.log(searchInsert([1, 3, 5, 6], 5)); // 2 console.log(searchInsert([1, 3, 5, 6], 2)); // 1 console.log(searchInsert([1, 3, 5, 6], 7)); // 4

Merge Two Sorted Lists (Linked Lists)

รวม linked lists ที่จัดเรียงแล้วสองรายการและส่งคืนเป็นรายการที่จัดเรียงใหม่ รายการใหม่ควรสร้างขึ้นโดยการต่อโหนดของสองรายการแรกเข้าด้วยกัน

คำอธิบาย: ใช้ dummy head node เพื่อทำให้กระบวนการง่ายขึ้น ใช้ตัวชี้เพื่อสร้างรายการใหม่ เปรียบเทียบโหนดปัจจุบันของรายการอินพุตทั้งสองและเพิ่มโหนดที่เล็กกว่าลงในรายการใหม่, เลื่อนตัวชี้ของรายการนั้น ทำซ้ำจนกว่ารายการหนึ่งจะหมด, จากนั้นเพิ่มส่วนที่เหลือของอีกรายการ

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

กำหนด binary tree, ตรวจสอบว่าเป็น mirror ของตัวเองหรือไม่ (เช่น symmetric รอบศูนย์กลาง)

คำอธิบาย: สิ่งนี้สามารถแก้ไขได้ด้วยการเรียกซ้ำ ต้นไม้จะสมมาตรหาก subtree ซ้ายเป็นภาพสะท้อนของ subtree ขวา สร้างฟังก์ชัน helper isMirror(t1, t2) ที่ตรวจสอบว่าต้นไม้สองต้นเป็น mirror หรือไม่ ควรตรวจสอบ: 1. t1.val === t2.val 2. t1.left เป็น mirror ของ t2.right 3. t1.right เป็น mirror ของ 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)

กำหนด binary tree, ส่งคืนการท่องระดับของค่าโหนด (เช่น จากซ้ายไปขวา, ระดับต่อระดับ)

คำอธิบาย: นี่คือ Breadth-First Search (BFS) ใช้ queue เริ่มต้นด้วยการเพิ่ม root ไปยัง queue ในขณะที่ queue ไม่ว่างเปล่า, ให้ประมวลผลโหนดทั้งหมดที่ระดับปัจจุบัน สำหรับแต่ละโหนดที่ประมวลผลแล้ว, ให้เพิ่ม children (ถ้ามี) ไปยัง queue สำหรับระดับถัดไป

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

กำหนดอาร์เรย์ที่องค์ประกอบถูกจัดเรียงตามลำดับจากน้อยไปมาก, แปลงเป็น binary search tree (BST) ที่สมดุลความสูง

คำอธิบาย: ในการสร้าง BST ที่สมดุลความสูง, คุณควรเลือกองค์ประกอบกลางของอาร์เรย์เป็น root จากนั้น, สร้าง subtree ซ้ายจากครึ่งซ้ายของอาร์เรย์และ subtree ขวาจากครึ่งขวาแบบเรียกซ้ำ

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

คำอธิบาย: วิธีที่ง่ายคือการจัดเรียงอาร์เรย์แล้วเลือกองค์ประกอบที่ดัชนี n - k วิธีที่มีประสิทธิภาพมากขึ้นสำหรับอาร์เรย์ขนาดใหญ่เกี่ยวข้องกับการใช้ min-heap หรืออัลกอริทึมการเลือกเช่น Quickselect

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

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`

ใช้งานโครงสร้างข้อมูล Set พื้นฐาน (โดยไม่ต้องใช้ Set ในตัว) พร้อมเมธอด add, has, delete, และ size

คำอธิบาย: คุณสามารถใช้วัตถุ JavaScript (hash map) เป็นที่เก็บข้อมูลพื้นฐาน คีย์จะเป็นองค์ประกอบ Set (คุณอาจต้องจัดการวิธีการจัดเก็บประเภทต่างๆ และตรวจสอบให้แน่ใจว่าเป็นเอกลักษณ์เป็นคีย์)

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, สร้างสามเหลี่ยมของ Pascal numRows แรก

คำอธิบาย: ในสามเหลี่ยมของ Pascal, แต่ละตัวเลขคือผลรวมของตัวเลขสองตัวที่อยู่ข้างบนโดยตรง เริ่มต้นด้วย [[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 และคำ, ค้นหาว่าคำมีอยู่ในกริดหรือไม่ คำสามารถสร้างขึ้นจากตัวอักษรของเซลล์ที่อยู่ติดกันตามลำดับ, โดยที่เซลล์ ที่อยู่ติดกัน คือเพื่อนบ้านในแนวนอนหรือแนวตั้ง

คำอธิบาย: สิ่งนี้ต้องใช้ Depth First Search (DFS) พร้อม backtracking วนซ้ำผ่านแต่ละเซลล์ หากเซลล์ตรงกับตัวอักษรแรกของคำ, ให้เริ่ม DFS ฟังก์ชัน DFS จะตรวจสอบเพื่อนบ้าน, ตรวจสอบให้แน่ใจว่าตรงกับตัวอักษรถัดไปและยังไม่ได้เยี่ยมชมในเส้นทางปัจจุบัน หากเส้นทางล้มเหลว, ให้ backtrack โดยการยกเลิกการทำเครื่องหมายเซลล์

function exist(board, word) { const rows = board.length; const cols = board[0].length; function dfs(r, c, index) { if (index === word.length) return true; // 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, ค้นหา window ขั้นต่ำใน s ที่จะบรรจุตัวอักษรทั้งหมดใน t หากไม่มี window ดังกล่าวใน s ที่ครอบคลุมตัวอักษรทั้งหมดใน t, ให้ส่งคืนสตริงว่างเปล่า

คำอธิบาย: ใช้แนวทาง sliding window ด้วยตัวชี้สองตัว (left และ right) และ hash maps แผนที่หนึ่งเก็บจำนวนตัวอักษรที่ต้องการจาก t อีกแผนที่เก็บจำนวนตัวอักษรใน window ปัจจุบัน ขยาย window ด้วย right เมื่อ window มีตัวอักษรที่ต้องการทั้งหมด, ให้ลองหดด้วย left เพื่อค้นหา window ที่เล็กที่สุดที่เป็นไปได้

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 ของ singly linked list, reverse รายการ, และส่งคืนรายการที่ reverse แล้ว

คำอธิบาย: คุณต้องวนซ้ำผ่านรายการ, เปลี่ยนตัวชี้ 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, head ของ linked list, ตรวจสอบว่า linked list มี cycle หรือไม่

คำอธิบาย: ใช้อัลกอริทึม Floyd's Tortoise and Hare มีตัวชี้สองตัว, ตัวหนึ่งเคลื่อนที่หนึ่งขั้นต่อครั้ง (slow) และอีกตัวหนึ่งเคลื่อนที่สองขั้นต่อครั้ง (fast) หากมี cycle, ตัวชี้ 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 สร้างวัตถุใหม่, โดยใช้วัตถุที่มีอยู่เป็น prototype ของวัตถุที่สร้างขึ้นใหม่ คุณสามารถทำได้โดยการสร้างฟังก์ชัน constructor ชั่วคราว, กำหนด prototype ของมันเป็น 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?

อธิบาย Hoisting ใน JavaScript และยกตัวอย่าง

คำอธิบาย: Hoisting เป็นพฤติกรรมเริ่มต้นของ JavaScript ในการย้ายการประกาศไปที่ด้านบนของขอบเขตปัจจุบัน (global หรือ function) ก่อนการประมวลผลโค้ด การประกาศตัวแปร (var) จะถูก Hoist และเริ่มต้นด้วย undefined การประกาศฟังก์ชันจะถูก Hoist ทั้งหมด (ทั้งชื่อและเนื้อหา) let และ const จะถูก Hoist แต่ไม่ได้รับการเริ่มต้น, ทำให้เกิด 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

อธิบาย Event Bubbling และ Capturing ในบริบทของ DOM

คำอธิบาย: นี่คือสองเฟสของการแพร่กระจายเหตุการณ์ใน HTML DOM Capturing Phase: เหตุการณ์จะเดินทางลงมาจาก window ไปยังองค์ประกอบเป้าหมาย Bubbling Phase: เหตุการณ์จะเดินทางขึ้นจากองค์ประกอบเป้าหมายกลับไปยัง window โดยค่าเริ่มต้น, ตัวจัดการเหตุการณ์ส่วนใหญ่จะถูกลงทะเบียนในระหว่างเฟส bubbling คุณสามารถใช้ addEventListener(type, listener, useCapture) โดยมี useCapture = true เพื่อจัดการเหตุการณ์ในระหว่างเฟส capturing

// 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 สามารถ cheat สิ่งนี้ได้แต่อันตราย ตัวแยกวิเคราะห์จริงจะใช้ lexer/tokenizer และ parser

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

Flatten a Deeply Nested Array

เขียนฟังก์ชันเพื่อยุบอาร์เรย์ที่ซ้อนกันลึกๆ

คำอธิบาย: แตกต่างจากการยุบก่อนหน้า (หนึ่งระดับ), นี่ต้องจัดการการซ้อนกันทุกระดับ Recursion เหมาะสมตามธรรมชาติ วนซ้ำผ่านอาร์เรย์ หากองค์ประกอบเป็นอาร์เรย์, เรียกใช้ 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)

ใช้งานโครงสร้างข้อมูล Trie พร้อมเมธอด insert, search, และ startsWith

คำอธิบาย: Trie เป็นโครงสร้างข้อมูลคล้ายต้นไม้ที่ใช้ในการจัดเก็บและเรียกใช้คีย์ในชุดข้อมูลของสตริงได้อย่างมีประสิทธิภาพ แต่ละโหนดแสดงถึงอักขระ, และเส้นทางจาก root แสดงถึงคำหรือคำนำหน้า

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)

เขียนฟังก์ชันเพื่อสับเปลี่ยนอาร์เรย์ในตำแหน่งโดยใช้อัลกอริทึม Fisher-Yates (หรือ Knuth)

คำอธิบาย: อัลกอริทึม Fisher-Yates วนซ้ำอาร์เรย์จากองค์ประกอบสุดท้ายไปยังองค์ประกอบแรก ในแต่ละการวนซ้ำ, จะสลับองค์ประกอบปัจจุบันกับองค์ประกอบที่สุ่มเลือกจากจุดเริ่มต้นของอาร์เรย์จนถึงองค์ประกอบปัจจุบัน (รวม)

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

กำหนดอาร์เรย์ของนิกายเหรียญและจำนวนเงิน, ค้นหาจำนวนเหรียญขั้นต่ำที่ทำให้ได้จำนวนเงินนั้น สมมติว่ามีเหรียญแต่ละประเภทไม่จำกัด

คำอธิบาย: นี่คือปัญหา dynamic programming คลาสสิก สร้างอาร์เรย์ dp ที่ dp[i] เก็บเหรียญขั้นต่ำที่ต้องการสำหรับจำนวนเงิน i 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)

กำหนด binary search tree (BST), ค้นหา lowest common ancestor (LCA) ของโหนดสองโหนดที่กำหนดใน BST

คำอธิบาย: LCA คือโหนดที่ลึกที่สุดที่มีโหนดที่กำหนดทั้งสองเป็นลูกหลาน ใน BST, คุณสามารถค้นหาได้โดยการวนจาก root หากโหนดทั้งสองเล็กกว่าโหนดปัจจุบัน, ไปทางซ้าย หากทั้งสองใหญ่กว่า, ไปทางขวา หากหนึ่งเล็กกว่าและอีกหนึ่งใหญ่กว่า (หรือหนึ่งคือโหนดปัจจุบัน), โหนดปัจจุบันคือ 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

ออกแบบอัลกอริทึมเพื่อ serialize และ deserialize binary tree

คำอธิบาย: Serialization แปลงต้นไม้เป็นสตริงหรืออาร์เรย์ Deserialization สร้างต้นไม้ขึ้นใหม่ วิธีทั่วไปคือ Pre-order Traversal (DFS) ใช้เครื่องหมายพิเศษ (เช่น '#') สำหรับ null nodes เพื่อรักษาสภาพโครงสร้าง

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`

ใช้งานฟังก์ชัน mySetInterval ที่เลียนแบบ setInterval แต่ใช้ setTimeout แบบเรียกซ้ำ

คำอธิบาย: 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)

ใช้งาน Breadth-First Search (BFS) และ Depth-First Search (DFS) สำหรับกราฟที่กำหนด (การแสดงรายการแบบ adjacency)

คำอธิบาย: BFS สำรวจเพื่อนบ้านก่อน (โดยใช้ Queue), ในขณะที่ DFS สำรวจให้ไกลที่สุดเท่าที่จะเป็นไปได้ตามแต่ละสาขา (โดยใช้ Stack หรือ recursion) ติดตามโหนดที่เยี่ยมชมเพื่อหลีกเลี่ยง cycles และการทำงานซ้ำซ้อน

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)

คุณได้รับเมทริกซ์ 2D ขนาด n x n ที่แสดงถึงภาพ หมุนภาพ 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]]

Spiral Matrix Traversal

กำหนดเมทริกซ์ 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]

Set Matrix Zeroes

กำหนดเมทริกซ์ m x n ถ้าองค์ประกอบเป็น 0 ให้ตั้งค่าทั้งแถวและคอลัมน์เป็น 0 ทำในที่เดียวกัน

คำอธิบาย: วิธีการแบบดั้งเดิมต้องใช้พื้นที่พิเศษ O(m*n) ในการทำในพื้นที่ O(1) (หรือ O(m+n)) คุณสามารถใช้แถวแรกและคอลัมน์แรกเพื่อเก็บข้อมูลว่าแถว/คอลัมน์ใดที่ต้องเป็นศูนย์ คุณต้องมีแฟล็กแยกต่างหากสำหรับว่าแถว/คอลัมน์แรกเองต้องเป็นศูนย์หรือไม่

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

Implement Promise.race

ใช้งานฟังก์ชันที่มีพฤติกรรมเหมือน Promise.race

คำอธิบาย: Promise.race รับอาร์เรย์ของ Promises และส่งคืน Promise เดียว Promise ที่ส่งคืนนี้จะเสร็จสิ้น (resolve หรือ reject) ทันทีที่ Promise ใดๆ ในอินพุตเสร็จสิ้น โดยมีค่าหรือเหตุผลจาก Promise นั้น

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

Singleton Pattern

ใช้งาน Singleton design pattern ใน JavaScript

คำอธิบาย: รูปแบบ Singleton ทำให้มั่นใจว่าคลาสมีอินสแตนซ์เดียวเท่านั้นและให้จุดเข้าถึงทั่วโลกแก่คลาสนั้น สิ่งนี้สามารถทำได้โดยใช้ closure เพื่อเก็บอินสแตนซ์

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'

Validate IP Address

เขียนฟังก์ชันเพื่อตรวจสอบว่าสตริงที่กำหนดเป็นที่อยู่ IPv4 หรือ IPv6 ที่ถูกต้องหรือไม่

คำอธิบาย: สำหรับ IPv4 ตรวจสอบตัวเลข 4 ตัว (0-255) ที่คั่นด้วยจุด โดยไม่มีเลขศูนย์นำหน้า (ยกเว้น 0 ตัวเดียว) สำหรับ IPv6 ตรวจสอบกลุ่ม 8 กลุ่มของเลขฐานสิบหก 1-4 หลักที่คั่นด้วยโคลอน (จัดการการเปลี่ยนแปลงเช่น '::') สามารถใช้ Regex ได้ แต่การแยกวิเคราะห์ด้วยตนเองมักจะชัดเจนกว่าสำหรับการสัมภาษณ์

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

Find Peak Element

องค์ประกอบสูงสุดคือองค์ประกอบที่มากกว่าเพื่อนบ้านอย่างเคร่งครัด กำหนดอาร์เรย์อินพุต 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)

Counting Bits

กำหนดจำนวนเต็ม n ส่งคืนอาร์เรย์ ans ที่มีความยาว n + 1 โดยที่สำหรับแต่ละ i (0 <= i <= n), ans[i] คือจำนวน 1 ในการแทนค่าไบนารีของ i

คำอธิบาย: คุณสามารถแก้ปัญหานี้ได้โดยใช้การเขียนโปรแกรมเชิงพลวัต สังเกตความสัมพันธ์: dp[i] = dp[i >> 1] + (i & 1) จำนวน 1 ใน i คือจำนวน 1 ใน i ที่เลื่อนไปทางขวา (เช่น i/2) บวก 1 ถ้า i เป็นเลขคี่

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]

Power of Two

กำหนดจำนวนเต็ม n ส่งคืน true ถ้าเป็นเลขยกกำลังของสอง มิฉะนั้น ส่งคืน false

คำอธิบาย: เลขยกกำลังของสองในการแทนค่าไบนารีมีบิต 1 เพียงหนึ่งบิตเท่านั้น (เช่น 1=1, 2=10, 4=100, 8=1000) เทคนิคบิตไวส์ที่ฉลาดคือการตรวจสอบว่า n > 0 และ (n & (n - 1)) === 0 ถ้า n เป็นเลขยกกำลังของสอง n-1 จะมีบิตทั้งหมดที่ต่ำกว่า 1 นั้นถูกตั้งค่าเป็น 1 การ ANDing พวกมันจะส่งผลให้เป็น 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

Merge Intervals

กำหนดอาร์เรย์ของช่วงเวลา intervals โดยที่ 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]]

Word Break

กำหนดสตริง s และพจนานุกรมของสตริง wordDict ส่งคืน true ถ้า s สามารถแบ่งส่วนเป็นลำดับที่คั่นด้วยช่องว่างของคำศัพท์พจนานุกรมหนึ่งคำขึ้นไปได้

คำอธิบาย: ใช้การเขียนโปรแกรมเชิงพลวัต สร้างอาร์เรย์บูลีน dp โดยที่ dp[i] เป็นจริงถ้าสตริงย่อย s[0...i-1] สามารถแบ่งส่วนได้ dp[i] เป็นจริงถ้ามี j < i ที่ dp[j] เป็นจริงและสตริงย่อย s[j...i-1] อยู่ในพจนานุกรม

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

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

Reverse Words in a String

กำหนดสตริงอินพุต 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'

Query String Parser

เขียนฟังก์ชันเพื่อแยกวิเคราะห์สตริงคำถาม 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 }

Use Proxy for Validation

สาธิตวิธีใช้ Proxy object สำหรับการตรวจสอบคุณสมบัติของออบเจกต์

คำอธิบาย: Proxy ช่วยให้คุณสามารถสกัดกั้นและปรับแต่งการดำเนินการที่ดำเนินการกับออบเจกต์ได้ ใช้ set trap เพื่อตรวจสอบค่าก่อนที่จะกำหนดให้กับคุณสมบัติ โยนข้อผิดพลาดถ้าการตรวจสอบล้มเหลว

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

Meeting Rooms

กำหนดอาร์เรย์ของช่วงเวลาการประชุม [[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

Implement Promise.any

ใช้งานฟังก์ชันที่มีพฤติกรรมเหมือน Promise.any

คำอธิบาย: Promise.any รับอาร์เรย์ของ Promises และส่งคืน Promise เดียว Promise นี้จะ fulfilled ทันทีที่ Promise ใดๆ ในอินพุต fulfilled หาก Promises อินพุตทั้งหมดถูกปฏิเสธ จะถูกปฏิเสธด้วย 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

Observer Pattern

ใช้งานรูปแบบ Observer (Pub/Sub) ใน JavaScript

คำอธิบาย: รูปแบบ Observer กำหนดการพึ่งพาแบบหนึ่งต่อหลายระหว่างออบเจกต์ เมื่อออบเจกต์หนึ่ง (Subject) เปลี่ยนสถานะ ผู้พึ่งพาทั้งหมด (Observers) จะได้รับการแจ้งเตือนและอัปเดตโดยอัตโนมัติ Subject จะรักษารายการผู้สังเกตการณ์และมีวิธีการเพิ่ม ลบ และแจ้งเตือนพวกเขา

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!

Find Duplicate Number

กำหนดอาร์เรย์ของจำนวนเต็ม nums ที่มีจำนวนเต็ม n + 1 โดยที่แต่ละจำนวนเต็มอยู่ในช่วง [1, n] แบบรวม ให้ค้นหาจำนวนที่ซ้ำกันหนึ่งจำนวน

คำอธิบาย: เนื่องจากตัวเลขอยู่ในช่วง [1, n] และขนาดอาร์เรย์คือ n+1 จึงรับประกันว่ามีอย่างน้อยหนึ่งตัวที่ซ้ำกัน สิ่งนี้สามารถจับคู่กับปัญหา 'ค้นหาวัฏจักรในรายการที่เชื่อมโยง' (Floyd's Tortoise and Hare) ปฏิบัติต่อค่าอาร์เรย์เป็นตัวชี้: 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

Basic HTML Sanitizer

เขียนฟังก์ชันพื้นฐานเพื่อทำความสะอาดสตริง HTML โดยการลบแท็กที่อาจเป็นอันตราย (เช่น <script>) ในขณะที่รักษาแท็กที่ปลอดภัย (เช่น <p>, <b>) ไว้

คำอธิบาย: นี่เป็นหัวข้อที่ซับซ้อน และตัวทำความสะอาดในโลกแห่งความเป็นจริงนั้นซับซ้อน สำหรับการสัมภาษณ์ วิธีการพื้นฐานอาจเกี่ยวข้องกับการใช้ Regex เพื่อลบแท็กเฉพาะหรืออนุญาตเฉพาะรายการแท็กที่อนุญาต สิ่งนี้ไม่ปลอดภัยสำหรับการใช้งานจริง

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>

Edit Distance

กำหนดสตริงสองสตริง word1 และ word2 ส่งคืนจำนวนการดำเนินการขั้นต่ำ (แทรก ลบ หรือแทนที่) ที่จำเป็นในการแปลง word1 เป็น word2

คำอธิบาย: นี่เป็นปัญหาการเขียนโปรแกรมเชิงพลวัตแบบคลาสสิก (ระยะทาง Levenshtein) สร้างอาร์เรย์ 2 มิติ dp โดยที่ dp[i][j] คือระยะทางแก้ไขระหว่างอักขระ i ตัวแรกของ word1 และอักขระ j ตัวแรกของ word2 ความสัมพันธ์การเรียกซ้ำเกี่ยวข้องกับการพิจารณาต้นทุนของการแทรก การลบ และการแทนที่

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

Longest Increasing Subsequence (LIS)

กำหนดอาร์เรย์จำนวนเต็ม nums ส่งคืนความยาวของลำดับย่อยที่เพิ่มขึ้นอย่างเคร่งครัดที่ยาวที่สุด

คำอธิบาย: ใช้การเขียนโปรแกรมเชิงพลวัต ให้ dp[i] เป็นความยาวของ LIS ที่ลงท้ายด้วยดัชนี i ในการคำนวณ dp[i] ให้ค้นหา j < i ทั้งหมดที่ nums[j] < nums[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 Problem

ปริศนา 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..' ] ]

Use WeakMap for Private Data

สาธิตวิธีใช้ 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.'

Implement Promise.allSettled

ใช้งานฟังก์ชันที่มีพฤติกรรมเหมือน Promise.allSettled

คำอธิบาย: Promise.allSettled รับอาร์เรย์ของ Promises และส่งคืน Promise เดียว Promise นี้จะ fulfilled เมื่อ Promises อินพุตทั้งหมดได้ settled (fulfilled หรือ rejected) ค่า fulfilled คืออาร์เรย์ของออบเจกต์ โดยแต่ละออบเจกต์อธิบายผลลัพธ์ของ Promise หนึ่งตัว ({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'}]

Find the Median of Two Sorted Arrays

กำหนดอาร์เรย์ที่เรียงลำดับสองอาร์เรย์ nums1 และ nums2 ที่มีขนาด m และ n ตามลำดับ ให้ส่งคืนค่ามัธยฐานของอาร์เรย์ที่เรียงลำดับสองอาร์เรย์นั้น

คำอธิบาย: นี่เป็นปัญหาที่ยากที่มักจะแก้ได้ด้วยวิธีการค้นหาแบบไบนารีบนอาร์เรย์ที่เล็กกว่า เป้าหมายคือการแบ่งพาร์ติชันอาร์เรย์ทั้งสองเพื่อให้องค์ประกอบทั้งหมดทางด้านซ้ายน้อยกว่าหรือเท่ากับองค์ประกอบทั้งหมดทางด้านขวา และจำนวนองค์ประกอบทางด้านซ้ายเท่ากับ (หรือมากกว่าหนึ่ง) ด้านขวา จากนั้นสามารถคำนวณค่ามัธยฐานจากองค์ประกอบขอบเขตได้

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

Sudoku Solver

เขียนโปรแกรมเพื่อไขปริศนา Sudoku โดยการเติมเซลล์ที่ว่างเปล่า

คำอธิบาย: ใช้การย้อนรอย ค้นหาเซลล์ที่ว่างเปล่า ลองเติมด้วยตัวเลข 1 ถึง 9 สำหรับแต่ละตัวเลข ให้ตรวจสอบว่าถูกต้องหรือไม่ (ไม่ละเมิดกฎ Sudoku) ถ้าถูกต้อง ให้เรียกใช้ solver ซ้ำๆ หากการเรียกซ้ำส่งคืน 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.

Implement a Basic Middleware Pattern

ใช้งานรูปแบบมิดเดิลแวร์พื้นฐานที่มักพบในเฟรมเวิร์กเว็บ

คำอธิบาย: ฟังก์ชันมิดเดิลแวร์ประมวลผลคำขอก่อนที่จะถึงตัวจัดการสุดท้าย มิดเดิลแวร์แต่ละตัวสามารถแก้ไขคำขอ/การตอบกลับ หรือส่งการควบคุมไปยังมิดเดิลแวร์ถัดไป สร้างคลาสหรือออบเจกต์ที่จัดการรายการฟังก์ชันมิดเดิลแวร์และดำเนินการตามลำดับ

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 }

Detect Cycle in Directed Graph

กำหนดกราฟที่มีทิศทาง เขียนฟังก์ชันเพื่อตรวจสอบว่ามีวัฏจักรหรือไม่

คำอธิบาย: ใช้ Depth First Search (DFS) รักษาสองชุด: visiting (โหนดที่อยู่ใน recursion stack ปัจจุบัน) และ visited (โหนดที่ได้รับการสำรวจอย่างสมบูรณ์) หากคุณพบโหนดในชุด visiting ระหว่าง DFS แสดงว่าคุณพบขอบย้อนกลับ ซึ่งหมายความว่ามีวัฏจักร

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

Implement Object.freeze behavior

อธิบาย 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 }

Use requestAnimationFrame for Smooth Animation

อธิบาย requestAnimationFrame และแสดงวิธีใช้สำหรับการวนรอบแอนิเมชันอย่างง่าย

คำอธิบาย: 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.');

Implement Array.prototype.some

ใช้งาน Array.prototype.some เวอร์ชันของคุณเอง

คำอธิบาย: some ทดสอบว่ามีองค์ประกอบอย่างน้อยหนึ่งองค์ประกอบในอาร์เรย์ที่ผ่านการทดสอบที่ใช้งานโดยฟังก์ชันที่ให้มาหรือไม่ มันส่งคืน true หากพบองค์ประกอบที่ฟังก์ชัน callback ส่งคืน 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

Implement 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

Implement 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

What are Web Workers?

อธิบายว่า Web Workers คืออะไรและกรณีการใช้งานหลักของมัน

คำอธิบาย: Web Workers เป็นวิธีการที่เนื้อหาเว็บสามารถรันสคริปต์ในเธรดพื้นหลังได้ กรณีการใช้งานหลักคือการปลดภาระงานที่ใช้เวลานานหรือต้องใช้การคำนวณมากจากเธรดหลัก (ซึ่งจัดการการอัปเดต UI และการโต้ตอบกับผู้ใช้) ซึ่งช่วยป้องกันไม่ให้ UI ไม่ตอบสนองหรือ 'ค้าง' Workers สื่อสารกับเธรดหลักผ่านการส่งข้อความ (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.');

Decode Ways

ข้อความที่มีตัวอักษรจาก 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

Bitwise Addition (without +)

เขียนฟังก์ชันเพื่อบวกจำนวนเต็มสองจำนวนโดยไม่ใช้ตัวดำเนินการ +

คำอธิบาย: ใช้ตัวดำเนินการแบบบิต ผลรวมสามารถคำนวณได้เป็น 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

Check for Palindrome (Number)

กำหนดจำนวนเต็ม x ส่งคืน true ถ้า x เป็น palindrome และ false มิฉะนั้น

คำอธิบาย: จำนวนลบไม่ใช่ palindrome แปลงตัวเลขเป็นสตริงและตรวจสอบว่าสตริงเป็น palindrome หรือไม่ (อ่านเหมือนกันไปข้างหน้าและข้างหลัง) หรืออีกทางหนึ่ง ให้ย้อนกลับตัวเลขทางคณิตศาสตร์แล้วเปรียบเทียบ

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

Factory Pattern

ใช้งานรูปแบบการออกแบบ Factory ใน JavaScript

คำอธิบาย: รูปแบบ Factory จัดเตรียมอินเทอร์เฟซสำหรับการสร้างวัตถุใน superclass แต่ให้ subclasses เปลี่ยนประเภทของวัตถุที่จะถูกสร้างขึ้น มีประโยชน์สำหรับการสร้างวัตถุโดยไม่ต้องระบุคลาสที่แน่นอน

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

Explain Symbol and a Use Case

อธิบายว่า Symbol คืออะไรใน JavaScript และระบุกรณีการใช้งาน เช่น การสร้างคุณสมบัติ 'ส่วนตัว' หรือคีย์วัตถุที่ไม่ซ้ำกัน

คำอธิบาย: 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)

Convert arguments to a Real Array

คุณจะแปลงออบเจกต์ arguments (ที่มีอยู่ในฟังก์ชันที่ไม่ใช่ลูกศร) ให้เป็นอาร์เรย์ JavaScript จริงได้อย่างไร

คำอธิบาย: ออบเจกต์ arguments ดูเหมือนอาร์เรย์แต่ไม่ใช่; ไม่มีเมธอดเช่น map, filter เป็นต้น คุณสามารถแปลงได้โดยใช้:

  1. Array.from(arguments) (ES6)
  2. [...arguments] (ES6 Spread Syntax)
  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