Algae-rithm 개발 λΈ”λ‘œκ·Έ
Published on

πŸ“š JavaScript μ•Œμ•„λ‘λ©΄ μœ μš©ν•œ ν•„μˆ˜ μ•Œκ³ λ¦¬μ¦˜

1. μˆ˜ν•™ κ΄€λ ¨ μ•Œκ³ λ¦¬μ¦˜

1.1 μ΅œλŒ€κ³΅μ•½μˆ˜ (GCD) - μœ ν΄λ¦¬λ“œ ν˜Έμ œλ²•

μ‹œκ°„ λ³΅μž‘λ„: O(log min(a, b))

// βœ… μž¬κ·€ 버전 (κ°€μž₯ κ°„κ²°)
function gcd(a, b) {
  return b === 0 ? a : gcd(b, a % b)
}

// βœ… 반볡 버전 (μŠ€νƒ μ˜€λ²„ν”Œλ‘œμš° λ°©μ§€)
function gcd(a, b) {
  while (b !== 0) {
    ;[a, b] = [b, a % b]
  }
  return a
}

// μ‹€μ „ μ˜ˆμ‹œ: 두 수의 μ΅œλŒ€κ³΅μ•½μˆ˜
gcd(48, 18) // 6

// μ‹€μ „ μ˜ˆμ‹œ: μ—¬λŸ¬ 수의 μ΅œλŒ€κ³΅μ•½μˆ˜
function gcdArray(arr) {
  return arr.reduce((a, b) => gcd(a, b))
}

// μ‹€μ „ μ˜ˆμ‹œ: λΆ„μˆ˜ κΈ°μ•½λΆ„μˆ˜ λ§Œλ“€κΈ°
function simplifyFraction(numerator, denominator) {
  const g = gcd(numerator, denominator)
  return [numerator / g, denominator / g]
}

주의점:

  • 음수 μž…λ ₯ μ‹œ μ ˆλŒ“κ°’μ„ μ·¨ν•΄μ•Ό ν•©λ‹ˆλ‹€: gcd(Math.abs(a), Math.abs(b))
  • a와 b 쀑 ν•˜λ‚˜κ°€ 0이면 λ‹€λ₯Έ ν•˜λ‚˜κ°€ μ΅œλŒ€κ³΅μ•½μˆ˜μž…λ‹ˆλ‹€.

1.2 μ΅œμ†Œκ³΅λ°°μˆ˜ (LCM)

μ‹œκ°„ λ³΅μž‘λ„: O(log min(a, b))

// βœ… GCDλ₯Ό μ΄μš©ν•œ LCM 계산
function lcm(a, b) {
  return (a * b) / gcd(a, b)
}

// μ‹€μ „ μ˜ˆμ‹œ: 두 수의 μ΅œμ†Œκ³΅λ°°μˆ˜
lcm(12, 18) // 36

// μ‹€μ „ μ˜ˆμ‹œ: μ—¬λŸ¬ 수의 μ΅œμ†Œκ³΅λ°°μˆ˜
function lcmArray(arr) {
  return arr.reduce((a, b) => lcm(a, b))
}

// μ‹€μ „ μ˜ˆμ‹œ: μ£ΌκΈ° μ°ΎκΈ°
function findCommonPeriod(period1, period2) {
  return lcm(period1, period2)
}

주의점:

  • 큰 수의 κ³±μ…ˆ μ‹œ μ˜€λ²„ν”Œλ‘œμš°λ₯Ό λ°©μ§€ν•˜λ €λ©΄: (a / gcd(a, b)) * b
  • BigIntκ°€ ν•„μš”ν•œ 경우: (BigInt(a) * BigInt(b)) / BigInt(gcd(a, b))

1.3 μ†Œμˆ˜ νŒλ³„

μ‹œκ°„ λ³΅μž‘λ„: O(√n)

// βœ… κΈ°λ³Έ μ†Œμˆ˜ νŒλ³„
function isPrime(n) {
  if (n < 2) return false
  if (n === 2) return true
  if (n % 2 === 0) return false

  for (let i = 3; i * i <= n; i += 2) {
    if (n % i === 0) return false
  }
  return true
}

// μ‹€μ „ μ˜ˆμ‹œ: μ†Œμˆ˜ 체크
isPrime(17) // true
isPrime(15) // false

주의점:

  • 2λŠ” μœ μΌν•œ 짝수 μ†Œμˆ˜μ΄λ―€λ‘œ 별도 μ²˜λ¦¬ν•©λ‹ˆλ‹€.
  • i * i <= n 쑰건으둜 √nκΉŒμ§€λ§Œ ν™•μΈν•©λ‹ˆλ‹€.

1.4 μ—λΌν† μŠ€ν…Œλ„€μŠ€μ˜ 체

μ‹œκ°„ λ³΅μž‘λ„: O(n log log n)

// βœ… μ—λΌν† μŠ€ν…Œλ„€μŠ€μ˜ 체
function sieveOfEratosthenes(n) {
  const isPrime = Array(n + 1).fill(true)
  isPrime[0] = isPrime[1] = false

  for (let i = 2; i * i <= n; i++) {
    if (isPrime[i]) {
      for (let j = i * i; j <= n; j += i) {
        isPrime[j] = false
      }
    }
  }

  return isPrime
}

// μ‹€μ „ μ˜ˆμ‹œ: n μ΄ν•˜μ˜ λͺ¨λ“  μ†Œμˆ˜ λ°˜ν™˜
function getPrimes(n) {
  const isPrime = sieveOfEratosthenes(n)
  return isPrime.map((prime, i) => (prime ? i : null)).filter((x) => x !== null)
}

// μ‹€μ „ μ˜ˆμ‹œ: μ†Œμˆ˜ 개수 μ„ΈκΈ°
function countPrimes(n) {
  const isPrime = sieveOfEratosthenes(n)
  return isPrime.filter((x) => x).length
}

// μ‹€μ „ μ˜ˆμ‹œ: μ†ŒμΈμˆ˜λΆ„ν•΄
function primeFactors(n) {
  const isPrime = sieveOfEratosthenes(n)
  const factors = []

  for (let i = 2; i * i <= n; i++) {
    if (isPrime[i] && n % i === 0) {
      while (n % i === 0) {
        factors.push(i)
        n /= i
      }
    }
  }

  if (n > 1) factors.push(n)
  return factors
}

주의점:

  • i * i <= nκΉŒμ§€λ§Œ ν™•μΈν•˜λ©΄ μΆ©λΆ„ν•©λ‹ˆλ‹€.
  • jλŠ” i * iλΆ€ν„° μ‹œμž‘ν•˜μ—¬ 쀑볡 체크λ₯Ό λ°©μ§€ν•©λ‹ˆλ‹€.

1.5 λ“±μ°¨μˆ˜μ—΄ (Arithmetic Sequence)

λ“±μ°¨μˆ˜μ—΄μ€ μ—°μ†ν•˜λŠ” 두 ν•­μ˜ 차이가 μΌμ •ν•œ μˆ˜μ—΄μž…λ‹ˆλ‹€. 첫 번째 항을 a1a_1, 곡차(common difference)λ₯Ό dd라고 ν•  λ•Œ:

  • μΌλ°˜ν•­: an=a1+(nβˆ’1)β‹…da_n = a_1 + (n-1) \cdot d
  • ν•© 곡식: Sn=n2β‹…(2a1+(nβˆ’1)d)=n2β‹…(a1+an)S_n = \frac{n}{2} \cdot (2a_1 + (n-1)d) = \frac{n}{2} \cdot (a_1 + a_n)

ν•© 곡식은 첫 ν•­κ³Ό λ§ˆμ§€λ§‰ ν•­μ˜ 평균에 ν•­μ˜ 개수λ₯Ό κ³±ν•œ 것과 κ°™μŠ΅λ‹ˆλ‹€. 예λ₯Ό λ“€μ–΄, 3,8,13,183, 8, 13, 18의 합은 42β‹…(3+18)=42\frac{4}{2} \cdot (3 + 18) = 42μž…λ‹ˆλ‹€.

μ‹œκ°„ λ³΅μž‘λ„: O(1)

// βœ… λ“±μ°¨μˆ˜μ—΄ ν•© κ΅¬ν•˜κΈ°
function arithmeticSum(firstTerm, commonDiff, n) {
  // S_n = n/2 * (2a_1 + (n-1)d)
  return (n * (2 * firstTerm + (n - 1) * commonDiff)) / 2
}

// βœ… 첫 ν•­κ³Ό λ§ˆμ§€λ§‰ ν•­μœΌλ‘œ ν•© κ΅¬ν•˜κΈ° (더 효율적)
function arithmeticSumWithLastTerm(firstTerm, lastTerm, n) {
  // S_n = n/2 * (a_1 + a_n)
  return (n * (firstTerm + lastTerm)) / 2
}

// μ‹€μ „ μ˜ˆμ‹œ: ν•© 계산
arithmeticSum(3, 5, 4) // 4/2 * (2*3 + (4-1)*5) = 2 * (6 + 15) = 42
arithmeticSumWithLastTerm(3, 18, 4) // 4/2 * (3 + 18) = 2 * 21 = 42

주의점:

  • 곡차(d)κ°€ 0이면 λͺ¨λ“  항이 같은 μƒμˆ˜ μˆ˜μ—΄μž…λ‹ˆλ‹€.
  • ν•© κ³΅μ‹μ—μ„œ λ‚˜λˆ—μ…ˆ μ‹œ μ •μˆ˜ λ‚˜λˆ—μ…ˆ 주의 (BigInt ν•„μš” μ‹œ μ‚¬μš©).
  • 음수 곡차도 κ°€λŠ₯ν•©λ‹ˆλ‹€ (κ°μ†Œν•˜λŠ” λ“±μ°¨μˆ˜μ—΄).

1.6 λ“±λΉ„μˆ˜μ—΄ (Geometric Sequence)

λ“±λΉ„μˆ˜μ—΄μ€ μ—°μ†ν•˜λŠ” 두 ν•­μ˜ λΉ„κ°€ μΌμ •ν•œ μˆ˜μ—΄μž…λ‹ˆλ‹€. 첫 번째 항을 a1a_1, 곡비(common ratio)λ₯Ό rr라고 ν•  λ•Œ:

  • μΌλ°˜ν•­: an=a1β‹…rnβˆ’1a_n = a_1 \cdot r^{n-1}
  • ν•© 곡식: Sn=a1β‹…rnβˆ’1rβˆ’1S_n = a_1 \cdot \frac{r^n - 1}{r - 1} (단, rβ‰ 1r \neq 1)
  • 곡비가 1인 경우: Sn=a1β‹…nS_n = a_1 \cdot n (λͺ¨λ“  항이 κ°™μŒ)
  • λ¬΄ν•œ λ“±λΉ„μˆ˜μ—΄ ν•© (∣r∣<1|r| < 1일 λ•Œ): S=a11βˆ’rS = \frac{a_1}{1 - r}

λ“±λΉ„μˆ˜μ—΄μ˜ 합을 ꡬ할 λ•ŒλŠ” rnr^n을 계산해야 ν•˜λ―€λ‘œ, λΉ λ₯Έ κ±°λ“­μ œκ³± μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•˜μ—¬ O(log n) μ‹œκ°„μ— κ³„μ‚°ν•©λ‹ˆλ‹€.

μ‹œκ°„ λ³΅μž‘λ„: O(log n) (κ±°λ“­μ œκ³± 계산 포함)

// βœ… λΉ λ₯Έ κ±°λ“­μ œκ³± (Binary Exponentiation)
function power(base, exp) {
  if (exp === 0) return 1
  if (exp === 1) return base

  let result = 1
  let current = base
  let remaining = exp

  while (remaining > 0) {
    if (remaining % 2 === 1) {
      result *= current
    }
    current *= current
    remaining = Math.floor(remaining / 2)
  }

  return result
}

// βœ… λ“±λΉ„μˆ˜μ—΄ ν•© κ΅¬ν•˜κΈ°
function geometricSum(firstTerm, commonRatio, n) {
  if (commonRatio === 1) {
    return firstTerm * n // 곡비가 1이면 λͺ¨λ“  항이 κ°™μŒ
  }

  // S_n = a_1 * (r^n - 1) / (r - 1)
  const rn = power(commonRatio, n)
  return (firstTerm * (rn - 1)) / (commonRatio - 1)
}

// βœ… λͺ¨λ“ˆλŸ¬ κ±°λ“­μ œκ³± (큰 수 처리)
function modPower(base, exp, mod) {
  if (exp === 0) return 1
  if (exp === 1) return base % mod

  let result = 1n
  let current = BigInt(base) % BigInt(mod)
  let remaining = BigInt(exp)

  while (remaining > 0n) {
    if (remaining % 2n === 1n) {
      result = (result * current) % BigInt(mod)
    }
    current = (current * current) % BigInt(mod)
    remaining = remaining / 2n
  }

  return Number(result)
}

// βœ… λͺ¨λ“ˆλŸ¬ μ—°μ‚°κ³Ό ν•¨κ»˜ μ‚¬μš© (큰 수 처리)
function geometricSumMod(firstTerm, commonRatio, n, mod) {
  if (commonRatio === 1) {
    return (firstTerm * n) % mod
  }

  const rn = modPower(commonRatio, n, mod)
  const numerator = (firstTerm * (rn - 1 + mod)) % mod // 음수 λ°©μ§€
  const denominator = (commonRatio - 1 + mod) % mod // 음수 λ°©μ§€

  // λͺ¨λ“ˆλŸ¬ 역원을 μ‚¬μš©ν•œ λ‚˜λˆ—μ…ˆ (페λ₯΄λ§ˆμ˜ μ†Œμ •λ¦¬: modκ°€ μ†Œμˆ˜μΌ λ•Œ)
  // (a / b) mod p = (a * b^(p-2)) mod p
  const invDenominator = modPower(denominator, mod - 2, mod)
  return (numerator * invDenominator) % mod
}

// μ‹€μ „ μ˜ˆμ‹œ: ν•© 계산
geometricSum(2, 3, 4) // 2 * (3^4 - 1) / (3 - 1) = 2 * (81 - 1) / 2 = 80

주의점:

  • 곡비(r)κ°€ 1이면 λͺ¨λ“  항이 같은 μƒμˆ˜ μˆ˜μ—΄μ΄λ―€λ‘œ Sn=a1β‹…nS_n = a_1 \cdot nμž…λ‹ˆλ‹€.
  • 곡비가 0이면 두 번째 ν•­λΆ€ν„° λͺ¨λ‘ 0μž…λ‹ˆλ‹€.
  • ν•© κ³΅μ‹μ—μ„œ r = 1인 경우λ₯Ό 별도 μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€.
  • 큰 수 계산 μ‹œ BigIntλ‚˜ λͺ¨λ“ˆλŸ¬ 연산을 μ‚¬μš©ν•©λ‹ˆλ‹€.
  • λ¬΄ν•œ λ“±λΉ„μˆ˜μ—΄ 합은 ∣r∣<1|r| < 1일 λ•Œλ§Œ μˆ˜λ ΄ν•©λ‹ˆλ‹€.

2. 탐색 μ•Œκ³ λ¦¬μ¦˜

μ‹œκ°„ λ³΅μž‘λ„: O(log n)

// βœ… κΈ°λ³Έ 이진 탐색 (μ •λ ¬λœ λ°°μ—΄)
function binarySearch(arr, target) {
  let left = 0
  let right = arr.length - 1

  while (left <= right) {
    const mid = Math.floor((left + right) / 2)

    if (arr[mid] === target) return mid
    if (arr[mid] < target) left = mid + 1
    else right = mid - 1
  }

  return -1 // μ°Ύμ§€ λͺ»ν•¨
}

// βœ… Lower Bound (target μ΄μƒμ˜ 첫 번째 μœ„μΉ˜)
function lowerBound(arr, target) {
  let left = 0
  let right = arr.length

  while (left < right) {
    const mid = Math.floor((left + right) / 2)
    if (arr[mid] < target) left = mid + 1
    else right = mid
  }

  return left
}

// βœ… Upper Bound (target 초과의 첫 번째 μœ„μΉ˜)
function upperBound(arr, target) {
  let left = 0
  let right = arr.length

  while (left < right) {
    const mid = Math.floor((left + right) / 2)
    if (arr[mid] <= target) left = mid + 1
    else right = mid
  }

  return left
}

// μ‹€μ „ μ˜ˆμ‹œ: μ •λ ¬λœ λ°°μ—΄μ—μ„œ μ°ΎκΈ°
binarySearch([1, 3, 5, 7, 9], 5) // 2

// μ‹€μ „ μ˜ˆμ‹œ: λ²”μœ„ μ°ΎκΈ°
function findRange(arr, target) {
  const lower = lowerBound(arr, target)
  const upper = upperBound(arr, target)
  return [lower, upper - 1] // [μ‹œμž‘ 인덱슀, 끝 인덱슀]
}

// μ‹€μ „ μ˜ˆμ‹œ: νŒŒλΌλ©”νŠΈλ¦­ μ„œμΉ˜ (쑰건을 λ§Œμ‘±ν•˜λŠ” μ΅œλŒ€/μ΅œμ†Œκ°’)
function parametricSearch(arr, condition) {
  let left = 0
  let right = arr.length - 1
  let result = -1

  while (left <= right) {
    const mid = Math.floor((left + right) / 2)
    if (condition(arr[mid])) {
      result = mid
      left = mid + 1 // μ΅œλŒ€κ°’ μ°ΎκΈ°
    } else {
      right = mid - 1
    }
  }

  return result
}

주의점:

  • 배열이 μ •λ ¬λ˜μ–΄ μžˆμ–΄μ•Ό ν•©λ‹ˆλ‹€.
  • left <= right vs left < right 쑰건에 따라 λ™μž‘μ΄ λ‹€λ¦…λ‹ˆλ‹€.
  • μ˜€λ²„ν”Œλ‘œμš° λ°©μ§€: mid = left + Math.floor((right - left) / 2)

2.2 투 포인터 (Two Pointers)

μ‹œκ°„ λ³΅μž‘λ„: O(n)

// βœ… 두 수의 ν•© μ°ΎκΈ° (μ •λ ¬λœ λ°°μ—΄)
function twoSum(arr, target) {
  let left = 0
  let right = arr.length - 1

  while (left < right) {
    const sum = arr[left] + arr[right]
    if (sum === target) return [left, right]
    if (sum < target) left++
    else right--
  }

  return null
}

// βœ… μ„Έ 수의 ν•© μ°ΎκΈ°
function threeSum(arr, target) {
  arr.sort((a, b) => a - b)
  const result = []

  for (let i = 0; i < arr.length - 2; i++) {
    if (i > 0 && arr[i] === arr[i - 1]) continue // 쀑볡 제거

    let left = i + 1
    let right = arr.length - 1

    while (left < right) {
      const sum = arr[i] + arr[left] + arr[right]
      if (sum === target) {
        result.push([arr[i], arr[left], arr[right]])
        while (left < right && arr[left] === arr[left + 1]) left++
        while (left < right && arr[right] === arr[right - 1]) right--
        left++
        right--
      } else if (sum < target) left++
      else right--
    }
  }

  return result
}

// βœ… μ΅œλŒ€ 길이 λΆ€λΆ„ λ°°μ—΄ (μŠ¬λΌμ΄λ”© μœˆλ„μš°μ™€ μœ μ‚¬)
function maxLengthSubarray(arr, condition) {
  let left = 0
  let maxLen = 0

  for (let right = 0; right < arr.length; right++) {
    // 쑰건을 λ§Œμ‘±ν•˜μ§€ μ•ŠμœΌλ©΄ left 이동
    while (!condition(arr.slice(left, right + 1))) {
      left++
    }
    maxLen = Math.max(maxLen, right - left + 1)
  }

  return maxLen
}

주의점:

  • 배열이 μ •λ ¬λ˜μ–΄ μžˆμ–΄μ•Ό νš¨μœ¨μ μž…λ‹ˆλ‹€.
  • 쀑볡 μ œκ±°κ°€ ν•„μš”ν•œ 경우 별도 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

2.3 μŠ¬λΌμ΄λ”© μœˆλ„μš° (Sliding Window)

μ‹œκ°„ λ³΅μž‘λ„: O(n)

// βœ… κ³ μ • 크기 μœˆλ„μš°
function maxSumSubarray(arr, k) {
  let windowSum = 0
  let maxSum = -Infinity

  // 초기 μœˆλ„μš°
  for (let i = 0; i < k; i++) {
    windowSum += arr[i]
  }
  maxSum = windowSum

  // μœˆλ„μš° μŠ¬λΌμ΄λ”©
  for (let i = k; i < arr.length; i++) {
    windowSum = windowSum - arr[i - k] + arr[i]
    maxSum = Math.max(maxSum, windowSum)
  }

  return maxSum
}

// βœ… κ°€λ³€ 크기 μœˆλ„μš° (μ΅œλŒ€ 길이)
function longestSubstringWithoutRepeating(s) {
  const seen = new Map()
  let left = 0
  let maxLen = 0

  for (let right = 0; right < s.length; right++) {
    const char = s[right]

    // 쀑볡 문자 발견 μ‹œ left 이동
    if (seen.has(char) && seen.get(char) >= left) {
      left = seen.get(char) + 1
    }

    seen.set(char, right)
    maxLen = Math.max(maxLen, right - left + 1)
  }

  return maxLen
}

// βœ… μ΅œμ†Œ 길이 λΆ€λΆ„ λ°°μ—΄
function minLengthSubarray(arr, target) {
  let left = 0
  let windowSum = 0
  let minLen = Infinity

  for (let right = 0; right < arr.length; right++) {
    windowSum += arr[right]

    while (windowSum >= target) {
      minLen = Math.min(minLen, right - left + 1)
      windowSum -= arr[left]
      left++
    }
  }

  return minLen === Infinity ? 0 : minLen
}

주의점:

  • μœˆλ„μš° 크기가 고정인지 가변인지에 따라 κ΅¬ν˜„μ΄ λ‹€λ¦…λ‹ˆλ‹€.
  • 쀑볡 μ²΄ν¬λŠ” Set λ˜λŠ” Map을 μ‚¬μš©ν•©λ‹ˆλ‹€.

3. κ·Έλž˜ν”„ μ•Œκ³ λ¦¬μ¦˜

3.1 Union-Find (Disjoint Set)

μ‹œκ°„ λ³΅μž‘λ„: 거의 O(1) (경둜 μ••μΆ• + 랭크 μ΅œμ ν™”)

class UnionFind {
  constructor(n) {
    this.parent = Array.from({ length: n }, (_, i) => i)
    this.rank = Array(n).fill(0)
  }

  // 경둜 압좕을 μ‚¬μš©ν•œ find
  find(x) {
    if (this.parent[x] !== x) {
      this.parent[x] = this.find(this.parent[x]) // 경둜 μ••μΆ•
    }
    return this.parent[x]
  }

  // 랭크λ₯Ό μ‚¬μš©ν•œ union
  union(x, y) {
    const rootX = this.find(x)
    const rootY = this.find(y)

    if (rootX === rootY) return false // 이미 같은 μ§‘ν•©

    // λž­ν¬κ°€ μž‘μ€ 트리λ₯Ό 큰 νŠΈλ¦¬μ— μ—°κ²°
    if (this.rank[rootX] < this.rank[rootY]) {
      this.parent[rootX] = rootY
    } else if (this.rank[rootX] > this.rank[rootY]) {
      this.parent[rootY] = rootX
    } else {
      this.parent[rootY] = rootX
      this.rank[rootX]++
    }

    return true
  }

  // 같은 집합인지 확인
  connected(x, y) {
    return this.find(x) === this.find(y)
  }
}

// μ‹€μ „ μ˜ˆμ‹œ: μ—°κ²° μš”μ†Œ 개수 μ°ΎκΈ°
function countComponents(n, edges) {
  const uf = new UnionFind(n)
  for (const [u, v] of edges) {
    uf.union(u, v)
  }

  const roots = new Set()
  for (let i = 0; i < n; i++) {
    roots.add(uf.find(i))
  }
  return roots.size
}

주의점:

  • 경둜 μ••μΆ•κ³Ό 랭크 μ΅œμ ν™”λ₯Ό λͺ¨λ‘ μ‚¬μš©ν•΄μ•Ό νš¨μœ¨μ μž…λ‹ˆλ‹€.
  • findμ—μ„œ μž¬κ·€ 호좜 μ‹œ 경둜 압좕이 μžλ™μœΌλ‘œ μˆ˜ν–‰λ©λ‹ˆλ‹€.

3.2 λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜ (Dijkstra)

μ‹œκ°„ λ³΅μž‘λ„: O((V + E) log V) (μš°μ„ μˆœμœ„ 큐 μ‚¬μš©)

// βœ… λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜
function dijkstra(graph, start) {
  const n = graph.length
  const dist = Array(n).fill(Infinity)
  const visited = Array(n).fill(false)
  dist[start] = 0

  // μš°μ„ μˆœμœ„ 큐: [거리, λ…Έλ“œ]
  const pq = [[0, start]]

  while (pq.length > 0) {
    // μ΅œμ†Œ 거리 λ…Έλ“œ 선택
    pq.sort((a, b) => a[0] - b[0])
    const [d, u] = pq.shift()

    if (visited[u]) continue
    visited[u] = true

    // 인접 λ…Έλ“œ μ—…λ°μ΄νŠΈ
    for (const [v, weight] of graph[u]) {
      if (!visited[v] && dist[u] + weight < dist[v]) {
        dist[v] = dist[u] + weight
        pq.push([dist[v], v])
      }
    }
  }

  return dist
}

// βœ… 인접 리슀트 κ·Έλž˜ν”„ μ˜ˆμ‹œ
// graph[i] = [[λ…Έλ“œ, κ°€μ€‘μΉ˜], ...]
const graph = [
  [
    [1, 4],
    [2, 1],
  ], // 0번 λ…Έλ“œ: 1번(κ°€μ€‘μΉ˜ 4), 2번(κ°€μ€‘μΉ˜ 1)
  [[3, 2]], // 1번 λ…Έλ“œ: 3번(κ°€μ€‘μΉ˜ 2)
  [
    [1, 2],
    [3, 5],
  ], // 2번 λ…Έλ“œ: 1번(κ°€μ€‘μΉ˜ 2), 3번(κ°€μ€‘μΉ˜ 5)
  [], // 3번 λ…Έλ“œ: μ—†μŒ
]

// μ‹€μ „ μ˜ˆμ‹œ: μ΅œλ‹¨ 경둜 μ°ΎκΈ°
const distances = dijkstra(graph, 0)
// [0, 3, 1, 5] - 0λ²ˆμ—μ„œ 각 λ…Έλ“œκΉŒμ§€μ˜ μ΅œλ‹¨ 거리

주의점:

  • 음수 κ°€μ€‘μΉ˜κ°€ 있으면 μ‚¬μš©ν•  수 μ—†μŠ΅λ‹ˆλ‹€ (벨만-ν¬λ“œ μ‚¬μš©).
  • μ‹€μ œλ‘œλŠ” νž™(Heap) 자료ꡬ쑰λ₯Ό μ‚¬μš©ν•˜λŠ” 것이 더 νš¨μœ¨μ μž…λ‹ˆλ‹€.
  • JavaScriptμ—μ„œλŠ” λ°°μ—΄ μ •λ ¬λ‘œ λŒ€μ²΄ν•  수 μžˆμ§€λ§Œ, μ„±λŠ₯이 λ–¨μ–΄μ§‘λ‹ˆλ‹€.

3.3 ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ μ•Œκ³ λ¦¬μ¦˜ (Floyd-Warshall)

μ‹œκ°„ λ³΅μž‘λ„: O(VΒ³)

// βœ… ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ μ•Œκ³ λ¦¬μ¦˜ (λͺ¨λ“  쌍 μ΅œλ‹¨ 경둜)
function floydWarshall(graph) {
  const n = graph.length
  const dist = graph.map((row) => [...row]) // 볡사

  // 쀑간 λ…Έλ“œλ₯Ό ν†΅ν•œ 경둜 μ—…λ°μ΄νŠΈ
  for (let k = 0; k < n; k++) {
    for (let i = 0; i < n; i++) {
      for (let j = 0; j < n; j++) {
        if (dist[i][k] !== Infinity && dist[k][j] !== Infinity) {
          dist[i][j] = Math.min(dist[i][j], dist[i][k] + dist[k][j])
        }
      }
    }
  }

  return dist
}

// βœ… 인접 ν–‰λ ¬ κ·Έλž˜ν”„ μ˜ˆμ‹œ
// graph[i][j] = iμ—μ„œ j둜 κ°€λŠ” κ°€μ€‘μΉ˜ (μ—†μœΌλ©΄ Infinity)
const graph = [
  [0, 4, 1, Infinity],
  [Infinity, 0, Infinity, 2],
  [Infinity, 2, 0, 5],
  [Infinity, Infinity, Infinity, 0],
]

// μ‹€μ „ μ˜ˆμ‹œ: λͺ¨λ“  쌍 μ΅œλ‹¨ 경둜
const allPairs = floydWarshall(graph)

주의점:

  • 음수 사이클이 있으면 감지할 수 μžˆμŠ΅λ‹ˆλ‹€ (dist[i][i] < 0).
  • 곡간 λ³΅μž‘λ„κ°€ O(VΒ²)μ΄λ―€λ‘œ 큰 κ·Έλž˜ν”„μ—μ„œλŠ” λΉ„νš¨μœ¨μ μž…λ‹ˆλ‹€.

4. μ‘°ν•©/μˆœμ—΄

4.1 μ‘°ν•© (Combination)

μ‹œκ°„ λ³΅μž‘λ„: O(C(n, r))

// βœ… μ‘°ν•© 생성 (n개 쀑 r개 선택)
function combinations(arr, r) {
  const result = []

  function backtrack(start, path) {
    if (path.length === r) {
      result.push([...path])
      return
    }

    for (let i = start; i < arr.length; i++) {
      path.push(arr[i])
      backtrack(i + 1, path)
      path.pop()
    }
  }

  backtrack(0, [])
  return result
}

// βœ… μ‘°ν•© 개수 계산 (nCr)
function combinationCount(n, r) {
  if (r > n || r < 0) return 0
  if (r === 0 || r === n) return 1

  let result = 1
  for (let i = 0; i < r; i++) {
    result = (result * (n - i)) / (i + 1)
  }
  return result
}

// μ‹€μ „ μ˜ˆμ‹œ: 5개 쀑 3개 선택
combinations([1, 2, 3, 4, 5], 3)
// [[1,2,3], [1,2,4], [1,2,5], [1,3,4], ...]

주의점:

  • 큰 수의 μ‘°ν•© 계산 μ‹œ BigIntκ°€ ν•„μš”ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
  • 쀑볡이 μžˆλŠ” 배열은 별도 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

4.2 μˆœμ—΄ (Permutation)

μ‹œκ°„ λ³΅μž‘λ„: O(n!)

// βœ… μˆœμ—΄ 생성 (n개λ₯Ό λͺ¨λ‘ λ‚˜μ—΄)
function permutations(arr) {
  const result = []

  function backtrack(path, used) {
    if (path.length === arr.length) {
      result.push([...path])
      return
    }

    for (let i = 0; i < arr.length; i++) {
      if (used[i]) continue

      used[i] = true
      path.push(arr[i])
      backtrack(path, used)
      path.pop()
      used[i] = false
    }
  }

  backtrack([], Array(arr.length).fill(false))
  return result
}

// βœ… μˆœμ—΄ 개수 계산 (n!)
function permutationCount(n) {
  let result = 1
  for (let i = 2; i <= n; i++) {
    result *= i
  }
  return result
}

// μ‹€μ „ μ˜ˆμ‹œ: 3개 μˆœμ—΄
permutations([1, 2, 3])
// [[1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1]]

주의점:

  • n!은 맀우 λΉ λ₯΄κ²Œ μ¦κ°€ν•˜λ―€λ‘œ μž‘μ€ nμ—λ§Œ μ‚¬μš© κ°€λŠ₯ν•©λ‹ˆλ‹€.
  • 쀑볡이 μžˆλŠ” 배열은 별도 μ²˜λ¦¬ν•©λ‹ˆλ‹€.

5. 트리 순회

5.1 DFS (깊이 μš°μ„  탐색)

// βœ… μž¬κ·€ DFS
function dfsRecursive(node, visited = new Set()) {
  if (!node || visited.has(node)) return

  visited.add(node)
  // λ…Έλ“œ 처리
  console.log(node.val)

  // μžμ‹ λ…Έλ“œ 순회
  for (const child of node.children) {
    dfsRecursive(child, visited)
  }
}

// βœ… μŠ€νƒμ„ μ‚¬μš©ν•œ 반볡 DFS
function dfsIterative(root) {
  const stack = [root]
  const visited = new Set()

  while (stack.length > 0) {
    const node = stack.pop()

    if (!node || visited.has(node)) continue
    visited.add(node)

    // λ…Έλ“œ 처리
    console.log(node.val)

    // μžμ‹ λ…Έλ“œλ₯Ό μ—­μˆœμœΌλ‘œ μŠ€νƒμ— μΆ”κ°€
    for (let i = node.children.length - 1; i >= 0; i--) {
      stack.push(node.children[i])
    }
  }
}

5.2 BFS (λ„ˆλΉ„ μš°μ„  탐색)

// βœ… 큐λ₯Ό μ‚¬μš©ν•œ BFS
function bfs(root) {
  const queue = [root]
  const visited = new Set()

  while (queue.length > 0) {
    const node = queue.shift()

    if (!node || visited.has(node)) continue
    visited.add(node)

    // λ…Έλ“œ 처리
    console.log(node.val)

    // μžμ‹ λ…Έλ“œλ₯Ό 큐에 μΆ”κ°€
    for (const child of node.children) {
      queue.push(child)
    }
  }
}

// βœ… λ ˆλ²¨λ³„ BFS
function bfsLevelOrder(root) {
  if (!root) return []

  const result = []
  const queue = [root]

  while (queue.length > 0) {
    const levelSize = queue.length
    const level = []

    for (let i = 0; i < levelSize; i++) {
      const node = queue.shift()
      level.push(node.val)

      for (const child of node.children) {
        queue.push(child)
      }
    }

    result.push(level)
  }

  return result
}

πŸ’‘ μ‹€μ „ ν™œμš© 팁

1. μ•Œκ³ λ¦¬μ¦˜ 선택 κ°€μ΄λ“œ

  • μ •λ ¬λœ λ°°μ—΄μ—μ„œ μ°ΎκΈ°: 이진 탐색
  • 두 수의 ν•©/μ°¨ μ°ΎκΈ°: 투 포인터
  • 연속 λΆ€λΆ„ λ°°μ—΄ μ΅œμ ν™”: μŠ¬λΌμ΄λ”© μœˆλ„μš°
  • κ·Έλž˜ν”„ μ—°κ²°μ„±: Union-Find
  • μ΅œλ‹¨ 경둜 (단일 좜발): λ‹€μ΅μŠ€νŠΈλΌ
  • μ΅œλ‹¨ 경둜 (λͺ¨λ“  쌍): ν”Œλ‘œμ΄λ“œ-μ›Œμ…œ
  • μ†Œμˆ˜ κ΄€λ ¨: μ—λΌν† μŠ€ν…Œλ„€μŠ€μ˜ 체
  • μ΅œλŒ€κ³΅μ•½μˆ˜/μ΅œμ†Œκ³΅λ°°μˆ˜: μœ ν΄λ¦¬λ“œ ν˜Έμ œλ²•

2. μ΅œμ ν™” 팁

  • BigInt μ‚¬μš©: 큰 수 계산 μ‹œ (μ‘°ν•©, μˆœμ—΄, μ΅œμ†Œκ³΅λ°°μˆ˜)
  • λ©”λͺ¨μ΄μ œμ΄μ…˜: 반볡 계산이 λ§Žμ€ 경우 (ν”Όλ³΄λ‚˜μΉ˜, μ‘°ν•©)
  • 경둜 μ••μΆ•: Union-Findμ—μ„œ ν•„μˆ˜
  • μ‘°κΈ° μ’…λ£Œ: 쑰건 만쑱 μ‹œ μ¦‰μ‹œ λ°˜ν™˜

3. 자주 μ‹€μˆ˜ν•˜λŠ” λΆ€λΆ„

  • 이진 νƒμƒ‰μ—μ„œ left <= right vs left < right ꡬ뢄
  • 투 ν¬μΈν„°μ—μ„œ 쀑볡 제거 처리
  • Union-Findμ—μ„œ 경둜 μ••μΆ• λˆ„λ½
  • λ‹€μ΅μŠ€νŠΈλΌμ—μ„œ 음수 κ°€μ€‘μΉ˜ 처리 λΆˆκ°€
  • μ‘°ν•©/μˆœμ—΄μ—μ„œ 큰 수 μ˜€λ²„ν”Œλ‘œμš°