Coding Test/Codility

16. νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜ (Greedy Algorithms) Codility Lesson ν•œκ΅­μ–΄ 정리본 (JavaScript ver.)

chamroro 2025. 3. 2. 16:32

νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜(Greedy Algorithm)μ΄λž€?

νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜μ€ 각 λ‹¨κ³„μ—μ„œ κ°€μž₯ μ΅œμ„ μ˜ 선택을 ν•˜λŠ” λ°©μ‹μœΌλ‘œ μ΅œμ ν•΄λ₯Ό μ°ΎλŠ” 방법 이닀. 전체 μ΅œμ ν•΄λ₯Ό κ³ λ €ν•˜μ§€ μ•Šκ³ , λ§€ μˆœκ°„ 졜적의 선택을 ν•˜κΈ° λ•Œλ¬Έμ— κ΅¬ν˜„μ΄ κ°„λ‹¨ν•˜μ§€λ§Œ, 항상 μ΅œμ ν•΄λ₯Ό 보μž₯ν•˜λŠ” 것은 μ•„λ‹ˆλ‹€.

νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜μ΄ 적용될 수 μžˆλŠ” λŒ€ν‘œμ μΈ λ¬Έμ œλ“€:

  • 동전 κ±°μŠ€λ¦„λˆ 문제 (Coin Change Problem)
  • νšŒμ˜μ‹€ λ°°μ • 문제 (Activity Selection Problem)
  • λ°°λ‚­ 문제 (Knapsack Problem)
  • μž‘μ—… μŠ€μΌ€μ€„λ§ 문제 (Activity Selection)
  • μ΅œμ†Œ μ‹ μž₯ 트리 (MST, Minimum Spanning Tree)
  • ν—ˆν”„λ§Œ μ½”λ”© (Huffman Coding)

νƒμš• μ•Œκ³ λ¦¬μ¦˜μ˜ νŠΉμ§•

  • λΉ λ₯Έ μ‹€ν–‰ μ‹œκ°„: O(n log n) λ˜λŠ” O(n)둜 ν•΄κ²° κ°€λŠ₯
  • κ΅­μ†Œμ  μ΅œμ ν•΄ 기반: ν˜„μž¬ μƒνƒœμ—μ„œ κ°€μž₯ 쒋은 선택을 ν•œλ‹€.
  • μ΅œμ ν•΄λ₯Ό 항상 보μž₯ν•˜μ§€ μ•ŠμŒ: 동전 κ±°μŠ€λ¦„λˆ 문제처럼 일뢀 κ²½μš°μ—λŠ” μ΅œμ ν•΄λ₯Ό ꡬ할 수 μ—†λ‹€.

4. νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜μ˜ ν•œκ³„

νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜μ€ λ§€ μˆœκ°„ 졜적의 선택을 ν•˜μ§€λ§Œ, ν•­μƒ 전체 μ΅œμ ν•΄λ₯Ό 보μž₯ν•˜λŠ” 것은 μ•„λ‹ˆλ‹€. λ”°λΌμ„œ λ‹€μŒκ³Ό 같은 κ²½μš°μ—λŠ” μ‚¬μš©ν•˜κΈ° μ–΄λ ΅λ‹€:

  • 졜적 λΆ€λΆ„ ꡬ쑰(Otimal Substructure) κ°€ μ—†λŠ” 경우 (예: λ°°λ‚­ 문제, 일뢀 동전 κ±°μŠ€λ¦„λˆ 문제)
  • κ²°μ •ν•΄μ•Ό ν•  μš”μ†Œλ“€μ΄ μ„œλ‘œ 영ν–₯을 λ―ΈμΉ˜λŠ” 경우

μ΄λŸ¬ν•œ λ¬Έμ œμ—μ„œλŠ” 동적 ν”„λ‘œκ·Έλž˜λ°(DP)μ΄λ‚˜ λ°±νŠΈλž˜ν‚Ή(Backtracking) 같은 λ‹€λ₯Έ 방법이 ν•„μš”ν•  수 μžˆλ‹€.


1. O(n) μ‹œκ°„ λ³΅μž‘λ„μ˜ κ±°μŠ€λ¦„λˆ 문제 ν•΄κ²°

κ±°μŠ€λ¦„λˆ λ¬Έμ œλŠ” μ£Όμ–΄μ§„ λ™μ „λ“€λ‘œ νŠΉμ • κΈˆμ•‘μ„ μ΅œμ†Œ 개수의 λ™μ „μœΌλ‘œ λ§Œλ“œλŠ” 문제 이닀. νƒμš•μ  μ•Œκ³ λ¦¬μ¦˜μ€ κ°€μž₯ 큰 동전뢀터 μ„ νƒν•˜λŠ” λ°©μ‹μœΌλ‘œ ν•΄κ²°ν•  수 μžˆλ‹€.

JavaScript μ½”λ“œ:

function coinChange(coins, amount) {
    coins.sort((a, b) => b - a); // λ‚΄λ¦Όμ°¨μˆœ μ •λ ¬
    let count = 0;
    
    for (let coin of coins) {
        while (amount >= coin) {
            amount -= coin;
            count++;
        }
    }
    return amount === 0 ? count : -1;
}

μ‹œκ°„ λ³΅μž‘λ„: O(n) (동전 개수만큼 반볡)

주의: 이 방법은 항상 μ΅œμ ν•΄λ₯Ό 보μž₯ν•˜μ§€ μ•ŠλŠ”λ‹€. 예λ₯Ό λ“€μ–΄, 동전이 [1, 3, 4]이고 amount = 6일 λ•Œ, νƒμš•μ  접근법은 [4, 1, 1]을 μ„ νƒν•˜μ§€λ§Œ, μ΅œμ ν•΄λŠ” [3, 3]이닀.


2. O(n log n) μ‹œκ°„ λ³΅μž‘λ„μ˜ νšŒμ˜μ‹€ λ°°μ • 문제

νšŒμ˜μ‹€ λ°°μ • λ¬Έμ œλŠ” κ°€λŠ₯ν•œ ν•œ λ§Žμ€ 회의λ₯Ό μˆ˜μš©ν•˜κΈ° μœ„ν•΄, νšŒμ˜κ°€ κ²ΉμΉ˜μ§€ μ•Šλ„λ‘ λ°°μ •ν•˜λŠ” 문제 이닀. μ’…λ£Œ μ‹œκ°„μ΄ κ°€μž₯ λΉ λ₯Έ νšŒμ˜λΆ€ν„° μ„ νƒν•˜λ©΄ μ΅œμ ν•΄λ₯Ό 보μž₯ν•  수 μžˆλ‹€.

JavaScript μ½”λ“œ:

function maxMeetings(start, end) {
    let n = start.length;
    let meetings = [];
    
    for (let i = 0; i < n; i++) {
        meetings.push([start[i], end[i]]);
    }
    
    meetings.sort((a, b) => a[1] - b[1]); // μ’…λ£Œ μ‹œκ°„ κΈ°μ€€ μ •λ ¬
    
    let count = 0, lastEnd = 0;
    for (let [s, e] of meetings) {
        if (s >= lastEnd) {
            count++;
            lastEnd = e;
        }
    }
    return count;
}

μ‹œκ°„ λ³΅μž‘λ„: O(n log n) (μ •λ ¬ ν›„ μ„ ν˜• 탐색)


3. O(E log V) μ‹œκ°„ λ³΅μž‘λ„μ˜ λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜

λ‹€μ΅μŠ€νŠΈλΌ μ•Œκ³ λ¦¬μ¦˜(Dijkstra’s Algorithm)은 κ°€μ€‘μΉ˜κ°€ μžˆλŠ” κ·Έλž˜ν”„μ—μ„œ μΆœλ°œμ μœΌλ‘œλΆ€ν„° λͺ¨λ“  μ •μ κΉŒμ§€μ˜ μ΅œλ‹¨ 경둜λ₯Ό μ°ΎλŠ” μ•Œκ³ λ¦¬μ¦˜ 이닀. νƒμš•μ  방법을 μ‚¬μš©ν•˜μ—¬ λ§€ λ‹¨κ³„μ—μ„œ μ΅œλ‹¨ 거리λ₯Ό μ„ νƒν•œλ‹€.

JavaScript μ½”λ“œ:

class PriorityQueue {
    constructor() {
        this.queue = [];
    }
    enqueue(value, priority) {
        this.queue.push({ value, priority });
        this.queue.sort((a, b) => a.priority - b.priority);
    }
    dequeue() {
        return this.queue.shift().value;
    }
    isEmpty() {
        return this.queue.length === 0;
    }
}

function dijkstra(graph, start) {
    let distances = {};
    let pq = new PriorityQueue();
    
    for (let node in graph) {
        distances[node] = Infinity;
    }
    distances[start] = 0;
    pq.enqueue(start, 0);
    
    while (!pq.isEmpty()) {
        let current = pq.dequeue();
        for (let neighbor in graph[current]) {
            let newDist = distances[current] + graph[current][neighbor];
            if (newDist < distances[neighbor]) {
                distances[neighbor] = newDist;
                pq.enqueue(neighbor, newDist);
            }
        }
    }
    return distances;
}

μ‹œκ°„ λ³΅μž‘λ„: O(E log V) (μš°μ„ μˆœμœ„ 큐λ₯Ό μ΄μš©ν•œ κ΅¬ν˜„)

 

4. μΉ΄λˆ„ 문제 (Canoeist Problem)

문제

λ¬΄κ²Œκ°€ λ‹€λ₯Έ nλͺ…μ˜ μΉ΄λˆ„ μ„ μˆ˜κ°€ 있고, 각 μΉ΄λˆ„μ—λŠ” μ΅œλŒ€ 두 λͺ…κΉŒμ§€ νƒˆ 수 μžˆλ‹€. λͺ¨λ“  μ„ μˆ˜λ₯Ό μ΅œμ†Œν•œμ˜ μΉ΄λˆ„μ— λ°°μΉ˜ν•˜λŠ” 것이 λͺ©ν‘œμ΄λ‹€.

μ ‘κ·Ό 방법

  • κ°€μž₯ 무거운 μ„ μˆ˜(heavy)λ₯Ό μ°Ύκ³ , ν•¨κ»˜ νƒˆ 수 μžˆλŠ” κ°€μž₯ 무거운 κ°€λ²Όμš΄ μ„ μˆ˜(light)λ₯Ό μ°ΎλŠ”λ‹€.
  • 무거운 μ„ μˆ˜μ™€ κ°€λ²Όμš΄ μ„ μˆ˜λ₯Ό ν•¨κ»˜ νƒœμš΄λ‹€.
  • μœ„ 과정이 λΆˆκ°€λŠ₯ν•˜λ©΄ 무거운 μ„ μˆ˜λ§Œ λ”°λ‘œ νƒœμš΄λ‹€.

JavaScript μ½”λ“œ (νƒμš• μ•Œκ³ λ¦¬μ¦˜)

function greedyCanoeist(weights, maxWeight) { 
	weights.sort((a, b) => a - b); // 무게 κΈ°μ€€ μ •λ ¬ 
    let canoes = 0; 
    let i = 0, j = weights.length - 1; 
    while (i <= j) { 
    	if (weights[i] + weights[j] <= maxWeight) { 
        	i++; // κ°€λ²Όμš΄ μ‚¬λžŒκ³Ό ν•¨κ»˜ νƒœμš΄λ‹€. 
        } j--; // 무거운 μ‚¬λžŒμ€ 무쑰건 νƒœμ›€ 
        canoes++; 
    } 
    return canoes; 
}

μ‹œκ°„ λ³΅μž‘λ„: O(n log n) (μ •λ ¬) + O(n) (탐색) = O(n log n)


5. νƒμš• μ•Œκ³ λ¦¬μ¦˜μ˜ 증λͺ… (Greedy Algorithm Proof)

νƒμš• μ•Œκ³ λ¦¬μ¦˜μ΄ 항상 μ΅œμ ν•΄λ₯Ό 보μž₯ν•˜λ €λ©΄, μ„ νƒν•œ 결정이 전체 λ¬Έμ œμ—μ„œλ„ μ΅œμ ν•΄λ₯Ό 보μž₯ν•΄μ•Ό ν•œλ‹€. 이λ₯Ό μˆ˜ν•™μ  κ·€λ‚©λ²•μœΌλ‘œ 증λͺ…ν•  수 μžˆλ‹€.

  1. 첫 번째 선택이 μ΅œμ ν•΄μ˜ 일뢀라면, λ‚˜λ¨Έμ§€ λ¬Έμ œλ„ μ΅œμ ν•΄λ₯Ό 포함할 수 μžˆμ–΄μ•Ό ν•œλ‹€.
  2. 기쑴의 μ΅œμ ν•΄μ—μ„œ 첫 번째 선택을 λ‹€λ₯Έ κ°’μœΌλ‘œ 바꾸어도 λ™μΌν•œ μ„±λŠ₯을 μœ μ§€ν•  수 μžˆμ–΄μ•Ό ν•œλ‹€.
  3. λ§Œμ•½ 바꿨을 λ•Œ 더 λ‚˜μ€ 해닡이 λ‚˜μ˜¨λ‹€λ©΄, νƒμš•μ  선택이 잘λͺ»λ˜μ—ˆμŒμ„ μ˜λ―Έν•œλ‹€.

μœ„ 증λͺ…이 μ„±λ¦½ν•˜λ©΄ νƒμš• μ•Œκ³ λ¦¬μ¦˜μ„ μ‚¬μš©ν•΄λ„ μ΅œμ ν•΄λ₯Ό 보μž₯ν•  수 μžˆλ‹€.

 

λ°˜μ‘ν˜•